Русский 中文 Español Deutsch 日本語 Português
The checks a trading robot must pass before publication in the Market

The checks a trading robot must pass before publication in the Market

MetaTrader 5Examples | 1 August 2016, 09:30
73 341 77
MetaQuotes
MetaQuotes

Why products are checked before they are published in the Market

Before any product is published in the Market, it must undergo compulsory preliminary checks, as a small error in the expert or indicator logic can cause losses on the trading account. That is why we have developed a series of basic checks to ensure the required quality level of the Market products.

If any errors are identified by the Market moderators in the process of checking your product, you will have to fix all of them. This article considers the most frequent errors made by developers in their trading robots and technical indicators. We also recommend reading the following articles:


How to quickly catch and fix errors in trading robots

The strategy tester integrated into the platform allows not only to backtest trading systems, but also to identify logical and algorithmic errors made at the development stage of the trading robot. During testing, all messages about trade operations and identified errors are output to the tester Journal. It is convenient to analyze those messages in a special log Viewer, which can be called using a context menu command.


After testing the EA, open the viewer and enable the "Errors only" mode, as shown in the figure. If your trading robot contains errors, you will see them immediately. If no errors were detected the first time, perform a series of tests with different instruments/timeframes/input parameters and different values of the initial deposit. 99% of the errors can be identified by these simple techniques, and they will be discussed in this article.

For a detailed study of detected errors use the Debugging on History Data in the MetaEditor. This method allows to use the visual testing mode for not only monitoring the price charts and values of indicators in use, but also track the value of each variable of the program at each tick. Thus, you will be able to debug your trading strategy without having to spend weeks in real time.

Insufficient funds to perform trade operation

Before sending every trade order, it is necessary to check if there are enough funds on the account. Lack of funds to support the future open position or order is considered a blunder.

Keep in mind that even placing a pending order may require collateral — margin.


It is recommended to test a trading robot with a deliberately small size of the initial deposit, for example, 1 USD or 1 Euro.

If a check shows that there are insufficient funds to perform a trade operation, it is necessary to output an error message to the log instead of calling the OrderSend() function. Sample check:

MQL5

bool CheckMoneyForTrade(string symb,double lots,ENUM_ORDER_TYPE type)
  {
//--- Getting the opening price
   MqlTick mqltick;
   SymbolInfoTick(symb,mqltick);
   double price=mqltick.ask;
   if(type==ORDER_TYPE_SELL)
      price=mqltick.bid;
//--- values of the required and free margin
   double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   //--- call of the checking function
   if(!OrderCalcMargin(type,symb,lots,price,margin))
     {
      //--- something went wrong, report and return false
      Print("Error in ",__FUNCTION__," code=",GetLastError());
      return(false);
     }
   //--- if there are insufficient funds to perform the operation
   if(margin>free_margin)
     {
      //--- report the error and return false
      Print("Not enough money for ",EnumToString(type)," ",lots," ",symb," Error code=",GetLastError());
      return(false);
     }
//--- checking successful
   return(true);
  }

MQL4

bool CheckMoneyForTrade(string symb, double lots,int type)
  {
   double free_margin=AccountFreeMarginCheck(symb,type, lots);
   //-- if there is not enough money
   if(free_margin<0)
     {
      string oper=(type==OP_BUY)? "Buy":"Sell";
      Print("Not enough money for ", oper," ",lots, " ", symb, " Error code=",GetLastError());
      return(false);
     }
   //--- checking successful
   return(true);
  }

Invalid volumes in trade operations

Before sending trade orders, it is also necessary to check the correctness of the volumes specified in the orders. The number of lots that the EA is about to set in the order, must be checked before calling the OrderSend() function. The minimum and maximum allowed trading volumes for the symbols are specified in the Specification, as well as the volume step. In MQL5, these values can be obtained from the ENUM_SYMBOL_INFO_DOUBLE enumeration with the help of the SymbolInfoDouble() function.

SYMBOL_VOLUME_MIN

Minimal volume for a deal

SYMBOL_VOLUME_MAX

Maximal volume for a deal

SYMBOL_VOLUME_STEP

Minimal volume change step for deal execution

Example of function for checking the correctness of the volume

//+------------------------------------------------------------------+
//| Check the correctness of the order volume                        |
//+------------------------------------------------------------------+
bool CheckVolumeValue(double volume,string &description)
  {
//--- minimal allowed volume for trade operations
   double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   if(volume<min_volume)
     {
      description=StringFormat("Volume is less than the minimal allowed SYMBOL_VOLUME_MIN=%.2f",min_volume);
      return(false);
     }

//--- maximal allowed volume of trade operations
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
   if(volume>max_volume)
     {
      description=StringFormat("Volume is greater than the maximal allowed SYMBOL_VOLUME_MAX=%.2f",max_volume);
      return(false);
     }

//--- get minimal step of volume changing
   double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP);

   int ratio=(int)MathRound(volume/volume_step);
   if(MathAbs(ratio*volume_step-volume)>0.0000001)
     {
      description=StringFormat("Volume is not a multiple of the minimal step SYMBOL_VOLUME_STEP=%.2f, the closest correct volume is %.2f",
                               volume_step,ratio*volume_step);
      return(false);
     }
   description="Correct volume value";
   return(true);
  }


Limiting Number of Pending Orders

There can also be a limit on the number of active pending orders that can be simultaneously placed at an account. Example of the IsNewOrderAllowed() function, which checks if another pending order can be placed. 

//+------------------------------------------------------------------+
//| Check if another order can be placed                             |
//+------------------------------------------------------------------+
bool IsNewOrderAllowed()
  {
//--- get the number of pending orders allowed on the account
   int max_allowed_orders=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);

//--- if there is no limitation, return true; you can send an order
   if(max_allowed_orders==0) return(true);

//--- if we passed to this line, then there is a limitation; find out how many orders are already placed
   int orders=OrdersTotal();

//--- return the result of comparing
   return(orders<max_allowed_orders);
  }

The function is simple: get the allowed number of orders to the max_allowed_orders variable; and if its value is not equal to zero, compare with the current number of orders. However, this function does not consider another possible restriction - the limitation on the allowed total volume of open positions and pending orders on a specific symbol.


Limiting Number of Lots by a Specific Symbol

To get the size of open position by a specific symbol, first of all you need to select a position using the PositionSelect() function. And only after that you can request the volume of the open position using the PositionGetDouble(), it returns various properties of the selected position that have the double type. Let's write the PostionVolume() function to get the position volume by a given symbol.

//+------------------------------------------------------------------+
//| Return the size of position on the specified symbol              |
//+------------------------------------------------------------------+
double PositionVolume(string symbol)
  {
//--- try to select position by a symbol
   bool selected=PositionSelect(symbol);
//--- there is a position
   if(selected)
      //--- return volume of the position
      return(PositionGetDouble(POSITION_VOLUME));
   else
     {
      //--- report a failure to select position
      Print(__FUNCTION__," Failed to perform PositionSelect() for symbol ",
            symbol," Error ",GetLastError());
      return(-1);
     }
  }

For accounts that support hedging, it is necessary to iterate over all positions on the current instrument.

Before making a trade request for placing a pending order by a symbol, you should check the limitation on the total volume of open position and pending orders on one symbol - SYMBOL_VOLUME_LIMIT. If there is no limitation, then the volume of a pending order cannot exceed the maximum allowed volume that can be received using the SymbolInfoDouble() volume.

double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);
if(max_volume==0) volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);

However, this approach doesn't consider the volume of current pending orders by the specified symbol. Here is an example of a function that calculates this value:

//+------------------------------------------------------------------+
//|  returns the volume of current pending order by a symbol         |
//+------------------------------------------------------------------+
double   PendingsVolume(string symbol)
  {
   double volume_on_symbol=0;
   ulong ticket;
//---  get the number of all currently placed orders by all symbols
   int all_orders=OrdersTotal();

//--- get over all orders in the loop
   for(int i=0;i<all_orders;i++)
     {
      //--- get the ticket of an order by its position in the list
      if(ticket=OrderGetTicket(i))
        {
         //--- if our symbol is specified in the order, add the volume of this order
         if(symbol==OrderGetString(ORDER_SYMBOL))
            volume_on_symbol+=OrderGetDouble(ORDER_VOLUME_INITIAL);
        }
     }
//--- return the total volume of currently placed pending orders for a specified symbol
   return(volume_on_symbol);
  }

With the consideration of the open position and volume in pending orders, the final check will look the following way:

//+------------------------------------------------------------------+
//| Return the maximum allowed volume for an order on the symbol     |
//+------------------------------------------------------------------+
double NewOrderAllowedVolume(string symbol)
  {
   double allowed_volume=0;
//--- get the limitation on the maximal volume of an order
   double symbol_max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
//--- get the limitation on the volume by a symbol
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);

//--- get the volume of the open position by a symbol
   double opened_volume=PositionVolume(symbol);
   if(opened_volume>=0)
     {
      //--- if we have exhausted the volume
      if(max_volume-opened_volume<=0)
         return(0);

      //--- volume of the open position doesn't exceed max_volume
      double orders_volume_on_symbol=PendingsVolume(symbol);
      allowed_volume=max_volume-opened_volume-orders_volume_on_symbol;
      if(allowed_volume>symbol_max_volume) allowed_volume=symbol_max_volume;
     }
   return(allowed_volume);
  }


Setting the TakeProfit and StopLoss levels within the SYMBOL_TRADE_STOPS_LEVEL minimum level

Many experts trade using the TakeProfit and StopLoss orders with levels calculated dynamically at the moment of performing a buy or a sell. The TakeProfit order serves to close the position when the price moves in a favorable direction, while the StopLoss is used for limiting losses when the price moves in an unfavorable direction.

Therefore, the TakeProfit and StopLoss levels should be compared to the current price for performing the opposite operation:

  • Buying is done at the Ask price — the TakeProfit and StopLoss levels should be compared to the Bid price.
  • Selling is done at the Bid price — the TakeProfit and StopLoss levels should be compared to the Ask price.
Buying is done at the Ask price
Selling is done at the Bid price
TakeProfit >= Bid
StopLoss <= Bid
TakeProfit <= Ask
StopLoss >= Ask



Financial instrument may have the SYMBOL_TRADE_STOPS_LEVEL parameter set in the symbol settings. It determines the number of points for minimum indentation of the StopLoss and TakeProfit levels from the current closing price of the open position. If the value of this property is zero, then the minimum indentation for SL/TP orders has not been set for buying and selling.

In general, checking the TakeProfit and StopLoss levels with the minimum distance of SYMBOL_TRADE_STOPS_LEVEL taken into account looks as follows:

  • Buying is done at the Ask price — the TakeProfit and StopLoss levels must be at the distance of at least SYMBOL_TRADE_STOPS_LEVEL points from the Bid price.
  • Selling is done at the Bid price — the TakeProfit and StopLoss levels must be at the distance of at least SYMBOL_TRADE_STOPS_LEVEL points from the Ask price.
Buying is done at the Ask price
Selling is done at the Bid price
TakeProfit - Bid >= SYMBOL_TRADE_STOPS_LEVEL
Bid - StopLoss >= SYMBOL_TRADE_STOPS_LEVEL
Ask - TakeProfit >= SYMBOL_TRADE_STOPS_LEVEL
StopLoss - Ask >= SYMBOL_TRADE_STOPS_LEVEL

So, we can create a CheckStopLoss_Takeprofit() check function, which requires the distance from the TakeProfit and StopLoss to the closing price to be at least SYMBOL_TRADE_STOPS_LEVEL points:

bool CheckStopLoss_Takeprofit(ENUM_ORDER_TYPE type,double SL,double TP)
  {
//--- get the SYMBOL_TRADE_STOPS_LEVEL level
   int stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   if(stops_level!=0)
     {
      PrintFormat("SYMBOL_TRADE_STOPS_LEVEL=%d: StopLoss and TakeProfit must"+
                  " not be nearer than %d points from the closing price",stops_level,stops_level);
     }
//---
   bool SL_check=false,TP_check=false;
//--- check only two order types
   switch(type)
     {
      //--- Buy operation
      case  ORDER_TYPE_BUY:
        {
         //--- check the StopLoss
         SL_check=(Bid-SL>stops_level*_Point);
         if(!SL_check)
            PrintFormat("For order %s StopLoss=%.5f must be less than %.5f"+
                        " (Bid=%.5f - SYMBOL_TRADE_STOPS_LEVEL=%d points)",
                        EnumToString(type),SL,Bid-stops_level*_Point,Bid,stops_level);
         //--- check the TakeProfit
         TP_check=(TP-Bid>stops_level*_Point);
         if(!TP_check)
            PrintFormat("For order %s TakeProfit=%.5f must be greater than %.5f"+
                        " (Bid=%.5f + SYMBOL_TRADE_STOPS_LEVEL=%d points)",
                        EnumToString(type),TP,Bid+stops_level*_Point,Bid,stops_level);
         //--- return the result of checking
         return(SL_check&&TP_check);
        }
      //--- Sell operation
      case  ORDER_TYPE_SELL:
        {
         //--- check the StopLoss
         SL_check=(SL-Ask>stops_level*_Point);
         if(!SL_check)
            PrintFormat("For order %s StopLoss=%.5f must be greater than %.5f "+
                        " (Ask=%.5f + SYMBOL_TRADE_STOPS_LEVEL=%d points)",
                        EnumToString(type),SL,Ask+stops_level*_Point,Ask,stops_level);
         //--- check the TakeProfit
         TP_check=(Ask-TP>stops_level*_Point);
         if(!TP_check)
            PrintFormat("For order %s TakeProfit=%.5f must be less than %.5f "+
                        " (Ask=%.5f - SYMBOL_TRADE_STOPS_LEVEL=%d points)",
                        EnumToString(type),TP,Ask-stops_level*_Point,Ask,stops_level);
         //--- return the result of checking
         return(TP_check&&SL_check);
        }
      break;
     }
//--- a slightly different function is required for pending orders
   return false;
  }

The check itself can look like this:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- randomly get the type of operation
   int oper=(int)(GetTickCount()%2); // remainder of division by two is always either 0 or 1
   switch(oper)
     {
      //--- buy
      case  0:
        {
         //--- get the opening price and knowingly set invalid TP/SL
         double price=Ask;
         double SL=NormalizeDouble(Bid+2*_Point,_Digits);
         double TP=NormalizeDouble(Bid-2*_Point,_Digits);
         //--- perform a check
         PrintFormat("Buy at %.5f   SL=%.5f   TP=%.5f  Bid=%.5f",price,SL,TP,Bid);
         if(!CheckStopLoss_Takeprofit(ORDER_TYPE_BUY,SL,TP))
            Print("The StopLoss or TakeProfit level is incorrect!");
         //--- try to buy anyway, in order to see the execution result
         Buy(price,SL,TP);
        }
      break;
      //--- sell
      case  1:
        {
         //--- get the opening price and knowingly set invalid TP/SL
         double price=Bid;
         double SL=NormalizeDouble(Ask-2*_Point,_Digits);
         double TP=NormalizeDouble(Ask+2*_Point,_Digits);
         //--- perform a check
         PrintFormat("Sell at %.5f   SL=%.5f   TP=%.5f  Ask=%.5f",price,SL,TP,Ask);
         if(!CheckStopLoss_Takeprofit(ORDER_TYPE_SELL,SL,TP))
            Print("The StopLoss or TakeProfit level is incorrect!");
         //--- try to sell anyway, in order to see the execution result
         Sell(price,SL,TP);
        }
      break;
      //---
     }
  }

The example of the function can be found in the attached scripts Check_TP_and_SL.mq4 and Check_TP_and_SL.mq5. Example of execution:

MQL5
Check_TP_and_SL (EURUSD,H1) Buy at 1.11433   SL=1.11425   TP=1.11421  Bid=1.11423
Check_TP_and_SL (EURUSD,H1) SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss and TakeProfit must not nearer than 30 points from the closing price
Check_TP_and_SL (EURUSD,H1) For order ORDER_TYPE_BUY StopLoss=1.11425 must be less than 1.11393 (Bid=1.11423 - SYMBOL_TRADE_STOPS_LEVEL=30 points)
Check_TP_and_SL (EURUSD,H1) For order ORDER_TYPE_BUY TakeProfit=1.11421 must be greater than 1.11453 (Bid=1.11423 + SYMBOL_TRADE_STOPS_LEVEL=30 points)
Check_TP_and_SL (EURUSD,H1) The StopLoss or TakeProfit level is incorrect!
Check_TP_and_SL (EURUSD,H1) OrderSend error 4756
Check_TP_and_SL (EURUSD,H1) retcode=10016  deal=0  order=0
MQL4
Check_TP_and_SL EURUSD,H1:  Sell at 1.11430   SL=1.11445   TP=1.11449  Ask=1.11447
Check_TP_and_SL EURUSD,H1:  SYMBOL_TRADE_STOPS_LEVEL=1: StopLoss and TakeProfit must not nearer than 1 points from the closing price
Check_TP_and_SL EURUSD,H1:  For order ORDER_TYPE_SELL StopLoss=1.11445 must be greater than 1.11448  (Ask=1.11447 + SYMBOL_TRADE_STOPS_LEVEL=1 points)
Check_TP_and_SL EURUSD,H1:  For order ORDER_TYPE_SELL TakeProfit=1.11449 must be less than 1.11446  (Ask=1.11447 - SYMBOL_TRADE_STOPS_LEVEL=1 points)
Check_TP_and_SL EURUSD,H1:  The StopLoss or TakeProfit level is incorrect!
Check_TP_and_SL EURUSD,H1:  OrderSend error 130 

To simulate the situation with the invalid TakeProfit and StopLoss values, the Test_Wrong_TakeProfit_LEVEL.mq5 and Test_Wrong_StopLoss_LEVEL.mq5 experts can be found in the article attachments. They can be run only on a demo account. Study these examples in order to see the conditions when a successful buy operation is possible.

Example of the Test_Wrong_StopLoss_LEVEL.mq5 EA execution:

Test_Wrong_StopLoss_LEVEL.mq5
Point=0.00001 Digits=5
SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT
SYMBOL_TRADE_FREEZE_LEVEL=20: order or position modification is not allowed, if there are 20 points to the activation price
SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss and TakeProfit must not nearer than 30 points from the closing price
1. Buy 1.0 EURUSD at 1.11442 SL=1.11404 Bid=1.11430 ( StopLoss-Bid=-26 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11442 sl: 1.11404 [invalid stops]
2. Buy 1.0 EURUSD at 1.11442 SL=1.11404 Bid=1.11431 ( StopLoss-Bid=-27 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11442 sl: 1.11404 [invalid stops]
3. Buy 1.0 EURUSD at 1.11442 SL=1.11402 Bid=1.11430 ( StopLoss-Bid=-28 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11442 sl: 1.11402 [invalid stops]
4. Buy 1.0 EURUSD at 1.11440 SL=1.11399 Bid=1.11428 ( StopLoss-Bid=-29 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11440 sl: 1.11399 [invalid stops]
5. Buy 1.0 EURUSD at 1.11439 SL=1.11398 Bid=1.11428 ( StopLoss-Bid=-30 points ))
Buy 1.0 EURUSD done at 1.11439 with StopLoss=41 points (spread=12 + SYMBOL_TRADE_STOPS_LEVEL=30)

Example of the Test_Wrong_TakeProfit_LEVEL.mq5 EA execution:

Test_Wrong_TakeProfit_LEVEL.mq5
Point=0.00001 Digits=5
SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT
SYMBOL_TRADE_FREEZE_LEVEL=20: order or position modification is not allowed, if there are 20 points to the activation price
SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss and TakeProfit must not nearer than 30 points from the closing price
1. Buy 1.0 EURUSD at 1.11461 TP=1.11478 Bid=1.11452 (TakeProfit-Bid=26 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11478 [invalid stops]
2. Buy 1.0 EURUSD at 1.11461 TP=1.11479 Bid=1.11452 (TakeProfit-Bid=27 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11479 [invalid stops]
3. Buy 1.0 EURUSD at 1.11461 TP=1.11480 Bid=1.11452 (TakeProfit-Bid=28 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11480 [invalid stops]
4. Buy 1.0 EURUSD at 1.11461 TP=1.11481 Bid=1.11452 (TakeProfit-Bid=29 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11481 [invalid stops]
5. Buy 1.0 EURUSD at 1.11462 TP=1.11482 Bid=1.11452 (TakeProfit-Bid=30 points)
Buy 1.0 EURUSD done at 1.11462 with TakeProfit=20 points (SYMBOL_TRADE_STOPS_LEVEL=30 - spread=10)

Checking the StopLoss and TakeProfit levels in pending orders is much simpler, as these levels must be set based on the order opening price. That is, the checking of levels taking into account the SYMBOL_TRADE_STOPS_LEVEL minimum distance looks as follows: the TakeProfit and StopLoss levels must be at the distance of at least SYMBOL_TRADE_STOPS_LEVEL points from the order activation price.

BuyLimit and BuyStop
SellLimit and SellStop
TakeProfit - Open >= SYMBOL_TRADE_STOPS_LEVEL
Open - StopLoss >= SYMBOL_TRADE_STOPS_LEVEL
Open - TakeProfit >= SYMBOL_TRADE_STOPS_LEVEL
StopLoss - Open >= SYMBOL_TRADE_STOPS_LEVEL

The Test_StopLoss_Level_in_PendingOrders.mq5 EA makes a series of attempts to place the BuyStop and BuyLimt orders until the operation succeeds. With each successful attempt the StopLoss or TakeProfit level is shifted by 1 point in the right direction. Example of this EA execution:

Test_StopLoss_Level_in_PendingOrders.mq5
SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT
SYMBOL_TRADE_FREEZE_LEVEL=20: order or position modification is not allowed, if there are 20 points to the activation price
SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss and TakeProfit must not nearer than 30 points from the closing price
1. BuyStop 1.0 EURUSD at 1.11019 SL=1.10993 (Open-StopLoss=26 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11019 sl: 1.10993 [invalid stops]
2. BuyStop 1.0 EURUSD at 1.11019 SL=1.10992 (Open-StopLoss=27 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11019 sl: 1.10992 [invalid stops]
3. BuyStop 1.0 EURUSD at 1.11020 SL=1.10992 (Open-StopLoss=28 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11020 sl: 1.10992 [invalid stops]
4. BuyStop 1.0 EURUSD at 1.11021 SL=1.10992 (Open-StopLoss=29 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11021 sl: 1.10992 [invalid stops]
5. BuyStop 1.0 EURUSD at 1.11021 SL=1.10991 (Open-StopLoss=30 points)
BuyStop 1.0 EURUSD done at 1.11021 with StopLoss=1.10991 (SYMBOL_TRADE_STOPS_LEVEL=30)
 --------- 
1. BuyLimit 1.0 EURUSD at 1.10621 TP=1.10647 (TakeProfit-Open=26 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10621 tp: 1.10647 [invalid stops]
2. BuyLimit 1.0 EURUSD at 1.10621 TP=1.10648 (TakeProfit-Open=27 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10621 tp: 1.10648 [invalid stops]
3. BuyLimit 1.0 EURUSD at 1.10621 TP=1.10649 (TakeProfit-Open=28 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10621 tp: 1.10649 [invalid stops]
4. BuyLimit 1.0 EURUSD at 1.10619 TP=1.10648 (TakeProfit-Open=29 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10619 tp: 1.10648 [invalid stops]
5. BuyLimit 1.0 EURUSD at 1.10619 TP=1.10649 (TakeProfit-Open=30 points)
BuyLimit 1.0 EURUSD done at 1.10619 with TakeProfit=1.10649 (SYMBOL_TRADE_STOPS_LEVEL=30)

Examples of checking the TakeProfit and StopLoss levels in pending orders can be found in the attached source files: Check_TP_and_SL.mq4 and Check_TP_and_SL.mq5.


Attempt to modify order or position within the SYMBOL_TRADE_FREEZE_LEVEL freeze level

The SYMBOL_TRADE_FREEZE_LEVEL parameter may be set in the symbol specification. It shows the distance of freezing the trade operations for pending orders and open positions in points. For example, if a trade on financial instrument is redirected for processing to an external trading system, then a BuyLimit pending order may be currently too close to the current Ask price. And, if and a request to modify this order is sent at the moment when the opening price is close enough to the Ask price, it may happen so that the order will have been executed and modification will be impossible.

Therefore, the symbol specifications for pending orders and open positions may have a freeze distance specified, within which they cannot be modified. In general, before attempting to send a modification request, it is necessary to perform a check with consideration of the SYMBOL_TRADE_FREEZE_LEVEL:

Type of order/position
Activation price
Check
Buy Limit order
 Ask
Ask-OpenPrice >= SYMBOL_TRADE_FREEZE_LEVEL
Buy Stop order AskOpenPrice-Ask >= SYMBOL_TRADE_FREEZE_LEVEL
Sell Limit order BidOpenPrice-Bid >= SYMBOL_TRADE_FREEZE_LEVEL
Sell Stop order BidBid-OpenPrice >= SYMBOL_TRADE_FREEZE_LEVEL
Buy position
 BidTakeProfit-Bid >= SYMBOL_TRADE_FREEZE_LEVEL
Bid-StopLoss >= SYMBOL_TRADE_FREEZE_LEVEL
Sell position
 AskAsk-TakeProfit >= SYMBOL_TRADE_FREEZE_LEVEL
StopLoss-Ask >= SYMBOL_TRADE_FREEZE_LEVEL

The complete examples of functions for checking the SYMBOL_TRADE_FREEZE_LEVEL level of orders and positions can be found in the attached Check_FreezeLevel.mq5 and Check_FreezeLevel.mq4 scripts.

//--- check the order type
   switch(type)
     {
      //--- BuyLimit pending order
      case  ORDER_TYPE_BUY_LIMIT:
        {
         //--- check the distance from the opening price to the activation price
         check=((Ask-price)>freeze_level*_Point);
         if(!check)
            PrintFormat("Order %s #%d cannot be modified: Ask-Open=%d points < SYMBOL_TRADE_FREEZE_LEVEL=%d points",
                        EnumToString(type),ticket,(int)((Ask-price)/_Point),freeze_level);
         return(check);
        }
      //--- BuyLimit pending order
      case  ORDER_TYPE_SELL_LIMIT:
        {
         //--- check the distance from the opening price to the activation price
         check=((price-Bid)>freeze_level*_Point);
         if(!check)
            PrintFormat("Order %s #%d cannot be modified: Open-Bid=%d points < SYMBOL_TRADE_FREEZE_LEVEL=%d points",
                        EnumToString(type),ticket,(int)((price-Bid)/_Point),freeze_level);
         return(check);
        }
      break;
      //--- BuyStop pending order
      case  ORDER_TYPE_BUY_STOP:
        {
         //--- check the distance from the opening price to the activation price
         check=((price-Ask)>freeze_level*_Point);
         if(!check)
            PrintFormat("Order %s #%d cannot be modified: Ask-Open=%d points < SYMBOL_TRADE_FREEZE_LEVEL=%d points",
                        EnumToString(type),ticket,(int)((price-Ask)/_Point),freeze_level);
         return(check);
        }
      //--- SellStop pending order
      case  ORDER_TYPE_SELL_STOP:
        {
         //--- check the distance from the opening price to the activation price
         check=((Bid-price)>freeze_level*_Point);
         if(!check)
            PrintFormat("Order %s #%d cannot be modified: Bid-Open=%d points < SYMBOL_TRADE_FREEZE_LEVEL=%d points",
                        EnumToString(type),ticket,(int)((Bid-price)/_Point),freeze_level);
         return(check);
        }
      break;
     }

You can simulate a situation where there is an attempt to modify a pending order within the freeze level. To do that, open a demo account with financial instruments that have non-zero SYMBOL_TRADE_FREEZE_LEVEL level, then attach the Test_SYMBOL_TRADE_FREEZE_LEVEL.mq5 (Test_SYMBOL_TRADE_FREEZE_LEVEL.mq4) EA to the chart and manually place any pending order. The EA will automatically move the order as close to the current price as possible and will start making illegal modification attempts. It will play a sound alert using the PlaySound() function.


Errors that occur when working with symbols which have insufficient quote history

If an expert or indicator is launched on a chart with insufficient history, then there are two possibilities:

  1. the program checks for availability of the required history in all the required depth. If there are less bars than required, the program requests the missing data and completes its operation before the next tick arrives. This path is the most correct and it helps to avoid a lot of mistakes, such as array out of range or zero divide;
  2. the program does not make any checks and immediately starts its work, as if all the necessary history on all the required symbols and timeframes was available immediately upon request. This approach is fraught with many unpredictable errors.

You can try simulating this situation yourself. To do that, run the tested indicator or EA on the chart, then close the terminal and delete all history and start the terminal again. If there are no errors in the log after such a restart, then try changing the symbols and timeframes on the charts the program is running on. Many indicators give errors when started on weekly or monthly timeframes, which usually have a limited number of bars. Also, during a sudden change of the chart symbol (for example, from EURUSD to CADJPY), an indicator or an expert running on that chart may encounter an error caused by the absence of history required for its calculation.


Array out of Range

When working with arrays, the access to their elements is performed by the index number, which cannot be negative and must be less than the array size. The array size can be obtained using the ArraySize() function.

This error can be encountered while working with a dynamic array when its size has not been explicitly defined by the ArrayResize() function, or when using such an array in functions that independently set the size of the dynamic arrays passed to them. For example, the CopyTicks() function tries to store the requested number of ticks to an array, but if there are less ticks than requested, the size of resulting array will be smaller than expected.

Another quite obvious way to get this error is to attempt to access the data of an indicator buffer while its size has not been initialized yet. As a reminder, the indicator buffers are dynamic arrays, and their sizes are defined by the terminal's execution system only after the chart initialization. Therefore, for instance, an attempt to access the data of such a buffer in the OnInit() function causes an "array out of range" error.


A simple example of an indicator that generates this error can be found in attached Test_Out_of_range.mq5 file.


Zero Divide

Another critical error is an attempt to divide by zero. In this case, the program execution is terminated immediately, the tester displays the name of the function and line number in the source code where the error occurred in the Journal.


As a rule, division by zero occurs due to a situation unforeseen by the programmer. For example, getting a property or evaluating an expression with "bad" data.

The zero divide can be easily reproduced using a simple TestZeroDivide.mq5 EA, its source code is displayed in the screenshot. Another critical error is the use of incorrect object pointer. The Debugging on History Data is useful for determining the cause of such error.


Sending a request to modify the levels without actually changing them

If the rules of the trading system require the pending orders or open positions to be modified, then before sending a trade request to perform a transaction, it is necessary to make sure that the requested operation would actually change parameters of the order or position. Sending a trade request which does not make any changes is considered an error. The trade server would respond to such action with a TRADE_RETCODE_NO_CHANGES=10025 return code (MQL5) or with an ERR_NO_RESULT=1 code (MQL4)

Example of the check in MQL5 is provided in the Check_OrderLevels.mq5 script:

//--- class for performing trade operations
#include <Trade\Trade.mqh>
CTrade trade;
#include <Trade\Trade.mqh>
//--- class for working with orders
#include <Trade\OrderInfo.mqh>
COrderInfo orderinfo;
//--- class for working with positions
#include <Trade\PositionInfo.mqh>
CPositionInfo positioninfo;
//+------------------------------------------------------------------+
//| Checking the new values of levels before order modification      |
//+------------------------------------------------------------------+
bool OrderModifyCheck(ulong ticket,double price,double sl,double tp)
  {
//--- select order by ticket
   if(orderinfo.Select(ticket))
     {
      //--- point size and name of the symbol, for which a pending order was placed
      string symbol=orderinfo.Symbol();
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
      //--- check if there are changes in the Open price
      bool PriceOpenChanged=(MathAbs(orderinfo.PriceOpen()-price)>point);
      //--- check if there are changes in the StopLoss level
      bool StopLossChanged=(MathAbs(orderinfo.StopLoss()-sl)>point);
      //--- check if there are changes in the Takeprofit level
      bool TakeProfitChanged=(MathAbs(orderinfo.TakeProfit()-tp)>point);
      //--- if there are any changes in levels
      if(PriceOpenChanged || StopLossChanged || TakeProfitChanged)
         return(true);  // order can be modified      
      //--- there are no changes in the Open, StopLoss and Takeprofit levels
      else
      //--- notify about the error
         PrintFormat("Order #%d already has levels of Open=%.5f SL=%.5f TP=%.5f",
                     ticket,orderinfo.PriceOpen(),orderinfo.StopLoss(),orderinfo.TakeProfit());
     }
//--- came to the end, no changes for the order
   return(false);       // no point in modifying 
  }
//+------------------------------------------------------------------+
//| Checking the new values of levels before order modification      |
//+------------------------------------------------------------------+
bool PositionModifyCheck(ulong ticket,double sl,double tp)
  {
//--- select order by ticket
   if(positioninfo.SelectByTicket(ticket))
     {
      //--- point size and name of the symbol, for which a pending order was placed
      string symbol=positioninfo.Symbol();
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      //--- check if there are changes in the StopLoss level
      bool StopLossChanged=(MathAbs(positioninfo.StopLoss()-sl)>point);
      //--- check if there are changes in the Takeprofit level
      bool TakeProfitChanged=(MathAbs(OrderTakeProfit()-tp)>point);
      //--- if there are any changes in levels
      if(StopLossChanged || TakeProfitChanged)
         return(true);  // position can be modified      
      //--- there are no changes in the StopLoss and Takeprofit levels
      else
      //--- notify about the error
         PrintFormat("Order #%d already has levels of Open=%.5f SL=%.5f TP=%.5f",
                     ticket,orderinfo.PriceOpen(),orderinfo.StopLoss(),orderinfo.TakeProfit());
     }
//--- came to the end, no changes for the order
   return(false);       // no point in modifying 
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- price levels for orders and positions
   double priceopen,stoploss,takeprofit;
//--- ticket of the current order and position
   ulong orderticket,positionticket;
/*
   ... get the order ticket and new StopLoss/Takeprofit/PriceOpen levels
*/
//--- check the levels before modifying the pending order   
   if(OrderModifyCheck(orderticket,priceopen,stoploss,takeprofit))
     {
      //--- checking successful
      trade.OrderModify(orderticket,priceopen,stoploss,takeprofit,
                        orderinfo.TypeTime(),orderinfo.TimeExpiration());
     }
/*
   ... get the position ticket and new StopLoss/Takeprofit levels
*/
//--- check the levels before modifying the position
   if(PositionModifyCheck(positionticket,stoploss,takeprofit))
     {
      //--- checking successful
      trade.PositionModify(positionticket,stoploss,takeprofit);
     }
//---
  }

Example of the check in the MQL4 language can be found in the Check_OrderLevels.mq4 script:

#property strict
//+------------------------------------------------------------------+
//| Checking the new values of levels before order modification      |
//+------------------------------------------------------------------+
bool OrderModifyCheck(int ticket,double price,double sl,double tp)
  {
//--- select order by ticket
   if(OrderSelect(ticket,SELECT_BY_TICKET))
     {
      //--- point size and name of the symbol, for which a pending order was placed
      string symbol=OrderSymbol();
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      //--- check if there are changes in the Open price
      bool PriceOpenChanged=true;
      int type=OrderType();
      if(!(type==OP_BUY || type==OP_SELL))
        {
         PriceOpenChanged=(MathAbs(OrderOpenPrice()-price)>point);
        }
      //--- check if there are changes in the StopLoss level
      bool StopLossChanged=(MathAbs(OrderStopLoss()-sl)>point);
      //--- check if there are changes in the Takeprofit level
      bool TakeProfitChanged=(MathAbs(OrderTakeProfit()-tp)>point);
      //--- if there are any changes in levels
      if(PriceOpenChanged || StopLossChanged || TakeProfitChanged)
         return(true);  // order can be modified      
      //--- there are no changes in the Open, StopLoss and Takeprofit levels
      else
      //--- notify about the error
         PrintFormat("Order #%d already has levels of Open=%.5f SL=%.5f TP=%.5f",
                     ticket,OrderOpenPrice(),OrderStopLoss(),OrderTakeProfit());
     }
//--- came to the end, no changes for the order
   return(false);       // no point in modifying 
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- price levels for orders and positions
   double priceopen,stoploss,takeprofit;
//--- ticket of the current order 
   int orderticket;
/*
   ... get the order ticket and new StopLoss/Takeprofit/PriceOpen levels
*/
//--- check the levels before modifying the order   
   if(OrderModifyCheck(orderticket,priceopen,stoploss,takeprofit))
     {
      //--- checking successful
      OrderModify(orderticket,priceopen,stoploss,takeprofit,OrderExpiration());
     }
  }

Recommended articles for reading:

  1. How to Make the Detection and Recovery of Errors in an Expert Advisor Code Easier
  2. How to Develop a Reliable and Safe Trade Robot in MQL 4


Attempt to import compiled files (even EX4/EX5) and DLL

The programs distributed via the Market must have a guaranteed safety for users. Therefore, any attempts to use and DLL or functions from compiled EX4/EX5 files are considered mistakes. These products will not be published in the Market.

If your program needs to use additional indicators not present in the standard delivery, use Resources.


Calling custom indicators with iCustom()

If the operation of your program requires calling to the data of a custom indicator, then all the necessary indicators should be placed to the Resources. Market products are supposed to be ready to work in any unprepared environment, so they should contain all everything they need within their EX4/EX5 files. Recommended related articles:


Passing an invalid parameter to the function (runtime error)

This type of errors is relatively rare, many of them have ready codes, that are designed to help in finding the cause.

Constant Value Description
ERR_INTERNAL_ERROR 4001 Unexpected internal error
ERR_WRONG_INTERNAL_PARAMETER 4002 Wrong parameter in the inner call of the client terminal function
ERR_INVALID_PARAMETER 4003 Wrong parameter when calling the system function
ERR_NOTIFICATION_WRONG_PARAMETER 4516 Invalid parameter for sending a notification — an empty string or NULL has been passed to the SendNotification() function
ERR_BUFFERS_WRONG_INDEX 4602 Wrong indicator buffer index
ERR_INDICATOR_WRONG_PARAMETERS 4808 Wrong number of parameters when creating an indicator
ERR_INDICATOR_PARAMETERS_MISSING 4809 No parameters when creating an indicator
ERR_INDICATOR_CUSTOM_NAME 4810 The first parameter in the array must be the name of the custom indicator
ERR_INDICATOR_PARAMETER_TYPE 4811 Invalid parameter type in the array when creating an indicator
ERR_NO_STRING_DATE 5030 No date in the string
ERR_WRONG_STRING_DATE 5031 Wrong date in the string
ERR_TOO_MANY_FORMATTERS 5038 Amount of format specifiers more than the parameters
ERR_TOO_MANY_PARAMETERS 5039 Amount of parameters more than the format specifiers

The table does not list all the errors that can me encountered during the operation of a program.

 

Access violation

This error occurs when trying to address memory, the access to which is denied. In each such case, it is necessary to contact the developers via the Service Desk in your Profile or via the Contacts page. A detailed description of the steps to reproduce the error and an attached source code will significantly accelerate the search for the causes of this error and will help to improve the source code compiler.




Consumption of the CPU resources and memory

When writing a program, use of time-optimal algorithms is essential, as otherwise the operation of other programs running in the terminal might become hindered or even impossible.

It is important to remember that the terminal allocates one common thread for working per each symbol in the Market watch. All the indicators running and charts opened for that symbol are processed in that thread.

This means that if there are 5 charts opened for EURUSD on different timeframes and there are 15 indicators running on those charts, then all these charts and indicators receive only a single thread for calculating and displaying information on the chart. Therefore, one inefficient resource-consuming indicator running on a chart may slow down the operation of all other indicators or even inhibit rendering of prices on all other charts of the symbol.

You can easily check the time taken by your algorithm using the GetMicrosecondCount() function. It is easy to get the execution time in microseconds by measuring the time between two lines of code. To convert this time into milliseconds (ms), it should be divided by 1000 (1 millisecond contains 1000 microseconds). Usually, the runtime bottleneck of indicators is the OnCalculate() handler. As a rule, the first calculation of the indicator is heavily dependent on the Max bars in chart parameter. Set it to "Unlimited" and run your indicator on a symbol with history of over 10 years on the M1 timeframe. If the first start takes too much time (for example, more than 100 ms), then code optimization is required.

Here is an example of measuring the execution time of the OnCalculate() handler in the ROC indicator, provided in the standard delivery of the terminal with source code. Insertions are highlighted in yellow:

//+------------------------------------------------------------------+
//| Rate of Change                                                   |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,const int prev_calculated,const int begin,const double &price[])
  {
//--- check for rates count
   if(rates_total<ExtRocPeriod)
      return(0);
//--- calculation start time
   ulong start=GetMicrosecondCount();  
//--- preliminary calculations
   int pos=prev_calculated-1; // set calc position
   if(pos<ExtRocPeriod)
      pos=ExtRocPeriod;
//--- the main loop of calculations
   for(int i=pos;i<rates_total && !IsStopped();i++)
     {
      if(price[i]==0.0)
         ExtRocBuffer[i]=0.0;
      else
         ExtRocBuffer[i]=(price[i]-price[i-ExtRocPeriod])/price[i]*100;
     }
//--- calculation end time     
   ulong finish=GetMicrosecondCount();  
   PrintFormat("Function %s in %s took %.1f ms",__FUNCTION__,__FILE__,(finish-start)/1000.);
//--- OnCalculate done. Return new prev_calculated.
   return(rates_total);
  }

The memory in use can be measured with the MQLInfoInteger(MQL_MEMORY_USED) function. And, of course, use the code Profiler to find the costliest functions in your program. We also recommend reading The Principles of Economic Calculation of Indicators and Debugging MQL5 Programs articles.

The Experts work in their own threads, but all of the above applies to them as well. Writing optimal code in any types of programs is essential, be it expert, indicator, library or script.


There cannot be too many checks

All of the above tips on checking the indicators and experts are recommended not only for publishing products in the Market, but also in common practice, when you write for yourself. This article did not cover all the errors that can be encountered during trading on real accounts. It did not consider the rules for handling the trade errors, which occur during loss of connection to the trading server, requotes, transaction rejections and many others that may disrupt the perfect rules of the trading system. Every robot developer has personal tried ready-made recipes for such cases.

Newcomers are recommended to read all the articles about error handling, as well as ask questions on the forum and in the article comments. Other more experienced members of the MQL5.community will help you figure out any unclear points. We hope that the information gathered in the article will help you create more reliable trading robots and in a shorter time.


Recommended related articles:

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

Attached files |
2555_en.zip (26.47 KB)
Last comments | Go to discussion (77)
Hiroki Fukumi
Hiroki Fukumi | 28 Dec 2023 at 02:59
I get the message "Strategy Tester Report not found" EURUSD 1H.  How can I solve this?

I've checked many times in strategy tester and my EA works without any errors.

I coded a "Validation Test" EA that simply opens a position with minimum allowed volume and closes the position after a few seconds, but same error did happen.

Abdulrazaq Ahmad Sabo
Abdulrazaq Ahmad Sabo | 25 Feb 2024 at 17:31

Just read a comment  regarding validation report saying "no trading activities". Must all EAs trade? My EA is just drawing stuffs on the chart, or it should had been an Indicator?

Daniel Gomes Alves
Daniel Gomes Alves | 12 Mar 2024 at 00:17

Anyone with this type of problem? In the Metaquotes DEMO account it works normally and in the test it did not pass validation.


test on EURUSD,H1 (netting) 2021.06.30 23:59:59 ExpertRemove() function called removed itself within OnDeinit removed itself on 99% of testing interval strategy tester report 1 total trades

Mohit Vijay Borse
Mohit Vijay Borse | 8 Apr 2024 at 07:42
i am getting a error while publishing my ea. someone help me with this
Sergey Golubev
Sergey Golubev | 8 Apr 2024 at 09:50
Mohit Vijay Borse #:
i am getting a error while publishing my ea. someone help me with this
"no trading operations" issue?
Graphical Interfaces VII: the Tables Controls (Chapter 1) Graphical Interfaces VII: the Tables Controls (Chapter 1)
The seventh part of the series on MetaTrader graphical interfaces deals with three table types: text label, edit box and rendered one. Another important and frequently used controls are tabs allowing you to show/hide groups of other controls and develop space effective interfaces in your MQL applications.
LifeHack for Traders: Indicators of Balance, Drawdown, Load and Ticks during Testing LifeHack for Traders: Indicators of Balance, Drawdown, Load and Ticks during Testing
How to make the testing process more visual? The answer is simple: you need to use one or more indicators in the Strategy Tester, including a tick indicator, an indicator of balance and equity, an indicator of drawdown and deposit load. This solution will help you visually track the nature of ticks, balance and equity changes, as well as drawdown and deposit load.
Testing trading strategies on real ticks Testing trading strategies on real ticks
The article provides the results of testing a simple trading strategy in three modes: "1 minute OHLC", "Every tick" and "Every tick based on real ticks" using actual historical data.
Universal Expert Advisor: Integration with Standard MetaTrader Modules of Signals (Part 7) Universal Expert Advisor: Integration with Standard MetaTrader Modules of Signals (Part 7)
This part of the article describes the possibilities of the CStrategy engine integration with the signal modules included into the standard library in MetaTrader. The article describes how to work with signals, as well as how to create custom strategies on their basis.