Русский 中文 Español Deutsch 日本語
Using Assertions in MQL5 Programs

Using Assertions in MQL5 Programs

MetaTrader 5Examples | 24 November 2015, 13:28
5 351 7
Sergey Eremin
Sergey Eremin

Introduction

Assertion is a special construction that enables checking arbitrary assumptions in the program's arbitrary places. They are typically embodied in the form of a code (mostly as a separate function or macro). This code checks a true value of a certain expression. If it appears to be false, then a relevant message is displayed, and the program is stopped, given the implementation provides for it. Accordingly, if the expression is true, it implies that everything operates as intended - the assumption is met. Otherwise, you can be certain, that the program has located errors and is clearly notifying about it.

For example, if it's expected that a certain value X within the program under no circumstance should be less than zero, then the following statement can be made: "I confirm that a value of X exceeds or equals zero". If X happens to be less than zero, then a relevant message will be displayed, and a programmer will be able to adjust the program.

Assertions are particularly useful in big projects, where their component parts may be reused or modified with time.

Assertions should cover only those situations that shouldn't occur during the program's regular operation. As a rule, assertions can be applied only at the program's development and debugging stages, i.e. they shouldn't be present in the final version. All assertions must be removed during the final version's compilation. This is usually achieved through conditional compilation.


Examples of the assertion mechanism in MQL5

The following capabilities are normally provided by the assertion mechanism:

  1. Display of an expression text to be checked.
  2. Display of a file name with a source code where an error is detected.
  3. Display the name and signature of a function or a method where an error is detected.
  4. Display of a line number in a source file where an expression is reviewed.
  5. Display of an arbitrary message specified by a programmer at the code writing stage.
  6. Program termination after finding errors.
  7. Possibility to exclude all assertions from the compiled program using conditional compilation or a similar mechanism.

Almost all capabilities can be implemented using standard functions (apart from point 6 — see further), macro and conditional compilation mechanisms of MQL5 language. For example, two out of all possible options might look the following way:

Option N1 (soft version, without terminating the program)

#define DEBUG

#ifdef DEBUG  
   #define assert(condition, message) \
      if(!(condition)) \
        { \
         string fullMessage= \
                            #condition+", " \
                            +__FILE__+", " \
                            +__FUNCSIG__+", " \
                            +"line: "+(string)__LINE__ \
                            +(message=="" ? "" : ", "+message); \
         \
         Alert("Assertion failed! "+fullMessage); \
        }
#else
   #define assert(condition, message) ;
#endif 

Option N2 (hard version, with terminating the program)

#define DEBUG

#ifdef DEBUG  
   #define assert(condition, message) \
      if(!(condition)) \
        { \
         string fullMessage= \
                            #condition+", " \
                            +__FILE__+", " \
                            +__FUNCSIG__+", " \
                            +"line: "+(string)__LINE__ \
                            +(message=="" ? "" : ", "+message); \
         \
         Alert("Assertion failed! "+fullMessage); \
         double x[]; \
         ArrayResize(x, 0); \
         x[1] = 0.0; \
        }
#else 
   #define assert(condition, message) ;
#endif


The assert macro

First, the DEBUG identifier is declared. If this identifier remains declared, then the #ifdef branch of the conditional compilation expression will be valid, and a full-featured macro assert will be included in the program. Otherwise, (#else branch) the assert macro that will not lead to the execution of any operation will be included in the program.

A full-featured macro assert is built the following way. First, an incoming expression condition is executed. If it is false, then a fullMessage message is formed and displayed. The fullMessage is constructed from the following elements:

  1. Text of an expression to be checked (#condition).
  2. File name with a source code from which the macro was called (__FILE__).
  3. Signature of a function or method from which the macro was called (__FUNCSIG__).
  4. Line number in a file with a source code where a macro call is placed (__LINE__).
  5. Message transfered to a macro, if it's not empty (message).

After displaying a message (Alert) an attempt to assign a value to a non-existing array element is made in the second macro type which leads to a time execution error and causes the program to crash immediately.

This method of stopping the program has side effects for indicators that operate in their subwindows: they remain in the terminal, therefore, need to be manually closed. Furthermore, there may be artifacts in the form of unremoved graphical objects, terminal's global variables, files, etc. that were created during the program's operation until it crashed. If this behavior is completely unacceptable, then the first macro should be used.

Explanation. At the moment of writing this article in MQL5 there was no mechanism for the program to perform an emergency stop. As an alternative, the runtime error was triggered, which guaranteed the program to crash.

This macro can be placed in the separate included file assert.mqh, which will be placed, for example, in <data folder>/MQL5/Include. This file (option N2) is attached to the article.

A code provided below contains an example of using assertion and its operation results.

Example of using the assert macro in the Expert Advisor's code

#include <assert.mqh>

int OnInit()
  {
   assert(0 > 1, "my message")   

   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {  
  }

void OnTick()
  {
  }

Here you can find an assertion that literally means "I confirm that 0 is greater than 1". The assertion is obviously false which leads to a display of an error message:

Fig. 1. Example of an assertion

Fig. 1. Example of an assertion


General principles for using assertions

Assertions should be used to identify the program's unforeseen situations and to document and control the execution of accepted assumptions. For example, assertions can be used to check the following conditions:

  • Values of input and output parameters, along with resulting values of functions and methods fall within the expected ranges.

    Example of using assertions to check input and output method values
    double CMyClass::SomeMethod(const double a)
      {
    //--- to check value of input parameter
       assert(a>=10,"")
       assert(a<=100,"")
    
    //--- to calculate the resulting value
       double result=...;
    
    //--- to check the resulting value
       assert(result>=0,"")
      
       return result;
      } 
    
    This example assumes that the input parameter a can't be less than 10 and more than 100. Furthermore, it is expected that the resulting value may not be less than zero.

  • The array's bounds are located within the expected range.

    Example of using assertions to check that array's bounds fall within the expected range
    void CMyClass::SomeMethod(const string &incomingArray[])
      {
    //--- checking array's bounds
       assert(ArraySize(incomingArray)>0,"")
       assert(ArraySize(incomingArray)<=10,"")
    
       ...
      }
    
    This example expects that the incomingArray array may contain at least one element, but is unable to exceed ten.

  • The descriptor of a created object is valid.

    Example of using assertions to check that the descriptor of a created object is valid
    void OnTick()
      {
    //--- creating object a
       CMyClass *a=new CMyClass();
    
    //--- some actions
       ...
       ...
       ...
    
    //--- checking that object a still exists
       assert(CheckPointer(a),"")
    
    //--- deleting object a
       delete a;
      } 
    
    This example assumes that in the end of the OnTick execution, the object a still exists.

  • Does a divisor equal zero in a division operation?

    Example of using assertions to check a divisor on inequality to zero
    void CMyClass::SomeMethod(const double a, const double b)
      {
    //--- checking that b doesn't equal zero
       assert(b!=0,"")
    
    //--- dividing a by b
       double c=a/b;
      
       ...  
       ...
       ...
      } 
    
    This example assumes that the input parameter b dividing a, doesn't equal zero.

There are certainly more types of conditions worth checking using assertions, and in each case they are absolutely unique. Some of them are shown above.

Use assertions for checking preconditions and postconditions. There is an approach for software design and development called "Design by contract". According to this approach, every function, method and class signs a contract with a remaining program's part using preconditions and postconditions.

Preconditions are agreements which a client code, calling method or class agrees to implement before calling a method or creating an object instance. In other words, if it is assumed that a method should take a certain parameter with values ​​greater than 10, then a programmer must take care of the calling code to ensure that under no circumstances it should pass a value less than or equal to 10 to this parameter.

Postconditions are agreements that a method or class agrees to implement before finishing their work. Thus, if it's expected that a method shouldn't return values below 100, then a programmer must take care of the returned value so it wouldn't exceed or equal 100.

It is very convenient to document preconditions and postconditions, and also monitor their compliance at the program's development and debugging stages. Unlike conventional comments, assertions will not simply declare expectations, but will also constantly monitor their fulfillment. An example of using assertions for checking and documenting preconditions and postconditions is shown above, read "Example of using assertions to check input and output method values".

If there is an opportunity, try to use assertions to check programming errors, that is, errors that are not affected by external factors. Thus, it's best not to use assertions for checking correctness of opening deals and availability of a quotation history for a certain requested period etc. It would be more appropriate to handle and log such errors.

Avoid placing an executable code in assertions. Since all assertions can be deleted at the compilation stage of the program's final version, they shouldn't affect the program's behavior. For example, this problem is frequently linked to calling a function or method inside the assert.

Assertion that can affect the program's behavior after disabling all assertions

void OnTick()

  {
   CMyClass someObject;

//--- to check some calculations for correctness
   assert(someObject.IsSomeCalculationsAreCorrect(),"")
  
   ...
   ...
   ...
  }

In this case you should place the function call before the assertion, save its result in a certain status variable and then check it in the assertion:

Assertion that can't affect the program's behavior after disabling all assertions

void OnTick()
  {
   CMyClass someObject;

//--- to check some calculations for correctness
   bool isSomeCalculationsAreCorrect = someObject.IsSomeCalculationsAreCorrect();
   assert(isSomeCalculationsAreCorrect,"")
  
   ...
   ...
   ...
  }

Don't confuse assertions with handling expected errors. Assertions are used for searching errors in programs at the development and debugging stages (searching of programing errors). Processing of expected errors, on the other hand, enables smooth operation of the program's released version (ideally shouldn't be programing errors). Assertions are not meant to handle errors, instead they should shout: "Hey, buddy, you have an error here!".

For example, in the event that a certain class method demands a value exceeding 10 to be handed over, and a programmer tries to pass 8, then this is an obvious error of a programmer and he should be warned about it:

Example of calling a method with an unacceptable value of input parameter (assertions are used for checking parameter's values)

void CMyClass::SomeMethod(const double a)

  {
//--- we are checking, if a exceeds 10
   assert(a>10,"")
  
   ...
   ...
   ...
  }

void OnTick()
  {
   CMyClass someObject;

   someObject.SomeMethod(8);
  
   ...
   ...
   ...
  }

Now, that a programmer runs a code with passing a value 8, the program clearly informs him: "I state that values below or equal to 10 can't be transfered to this method".

Figure 2. Operation results of calling a method with an unacceptable value of input parameter ​​(assertions are used to check parameter values)

Fig. 2. Operation results of calling a method with an unacceptable value of input parameter ​​(assertions are used to check parameter values)

After receiving such message a programmer can quickly correct the error.

In turn, if for a program's operation it is necessary that the history of an instrument exceeds 1000 bars, then the reverse situation can't be considered as a programmer's mistake, as the available history volume doesn't depend from him. It would be logical to handle a situation with less than 1000 bars available for an instrument as an expected error:

Example of handling a situation when the available history for an instrument is less than required (error processing is applied in this situation)

void OnTick()
  {
   if(Bars(Symbol(),Period())<1000)
     {
      Comment("Insufficient history for correct operation of the program");
      return;
     }
  }

For maximum stability of a final version please use assertions to check expectations and then, nevertheless, handle errors:

Example of a combination of assertions and error handling

double CMyClass::SomeMethod(const double a)
  {
//--- check value of an input parameter with assertion
   assert(a>=10,"")
   assert(a<=100,"")
  
//--- check value of an input parameter and correct it, if required
   double aValue = a;

   if(aValue<10)
     {
      aValue = 10;
     }
   else if(aValue>100)
     {
      aValue = 100;
     }

//--- calculate the resulting value
   double result=...;

//--- check a resulting value with assertion
   assert(result>=0,"")

//--- check a resulting value and correct it, if necessary
   if(result<0)
     {
      result = 0;
     }

   return result;
  } 


Thus, assertions help tracking a certain number of errors before the release of a final version. Handling errors in the final version will enable the program to operate correctly even with those mistakes, that couldn't be found at the development and debugging stages.

There are multiple methods of handling errors: from the correction of wrong values (as in the example above) to a full stop of an operation. Studying all of them would fall beyond the scope of this article.

It is also believed, that if the program reacts at a programmer's mistake with a correct crashing and indicates a problem, then assertions are redundant in this case. For example, at the division by zero the program in MQL5 terminates its execution and displays the relevant message in the log. In principle, such approach helps to find problems better than assertions do. However, assertions allow to add the important information about assumptions to the code, that will be more noticeable (in case of a violation) and appropriate than classic comments in the source code, and that can greatly assist in the further support and the code's development.


Conclusion

This article has studied the mechanism of assertions, provided an example of its implementation in MQL5, and also offered general recommendations about its application. Properly applied assertions can greatly simplify the software development and debugging stages.

Please remember that assertions are firstly targeting the search of programming errors (errors made by programmers), rather than errors not depending on programmers. Assertions are not included in the program's final version. For possible errors that are not depending on a programmer, it is best to use error handling.

The assertion mechanism is closely associated with a matter of software testing. Both error handling and software testing are vast subjects that deserve additional attention and should be covered in separate articles.

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

Attached files |
assert.mqh (2.03 KB)

Other articles by this author

Last comments | Go to discussion (7)
Sergey Eremin
Sergey Eremin | 6 Dec 2015 at 08:58

Or other example of EA with ExpertRemove(), not with cicle :)

For example, we must stop app if volume is bigger than we need (it just example, in real situation we should handle this situation differently):

#property version   "1.00"
#property strict

int OnInit()
  {
   OpenTrade();
//---
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {

      
  }

void OnTick()
  {
//---
   
  }
bool OpenTrade()
{
   double volume = GetVolume();
   
   if(volume > 1)
   {
      Print("Volume > 1, stop EA!");
      ExpertRemove();
   }
   
   Print("Opening position ...");
   
   return true;
}

double GetVolume()
{
   return 999.0;
}

We must stop EA momentally at needed place of code (in this is sense of assertions). But ExpertRemove() in this case not correct variant, we have:

2015.12.06 10:53:22.253    Test EURUSD,H1: Volume > 1, stop EA!
2015.12.06 10:53:22.253    Test EURUSD,H1: ExpertRemove function called
2015.12.06 10:53:22.253    Test EURUSD,H1: Opening position ...


So, please show to me how to use ExpertRemove() function (without return, break etc, it must be universally for any part of code) for this ceil.

Alain Verleyen
Alain Verleyen | 6 Dec 2015 at 11:34
Sergey Eremin:

Please, show to me an example, how to exit with ExpertRemove() in a cicle.

For example, we have this code:

We need exit if i == 2, and all other steps not must running. In journal we must see only "0" and "1". How we can do it with this function?

Right now ExpertRemove() not stop EA at needed moment, all steps will be running, and after this EA will be stopped. But this is wrong for Assertion mechanism, we must stop EA momentally. And yes, we cannot use just "break", because we need universal macro or function for any part of any EA's.

You don't have to use ExpertRemove() in OnInit(), just use return(INIT_FAILED);

int OnInit()
  {
//---
    ...

         if(somethign wrong)
           {
            //ExpertRemove();         
            return(INIT_FAILED);    //--- No need to use ExpertRemove() in OnInit()
           }
    ...
  }

in other part of the code, just return :

            ExpertRemove();
            return;           //--- Just return to finish the current event handler

or

            ExpertRemove();
            return(x);        //--- Just return to finish the current event handler

About indicators - please show to me universal mechanism to definition ShortName of indicators. Because without this mechanism we cannot use this function for Assertions. Yes, we can define ShortName in our concrete indictor (for example with global variable, like a lot people do it, even if it is bad practice), but we no have for example universal function "GetShortName()". So we cannot make universal mechanism (I mean some macro or function for absolutly any indicators, where we can add just one line "assert(...)") with ChartIndicatorDelete().

What's the problem ? You are working on your indicator, it's your code, so you know the short name.

My post was to say you are not right saying there is no way to terminate a program immediately. It's to you to find solution for your Assertion project.

This is absolutely not a bad practice to use global variable in an indicator. Of course if you want to create your self new limitation with such assertion "it is bad practice", you will find a lot of impossible things.

And please show to me "trivial" variant for Sripts for any parts of code. It must be one (!) function or macro for any part of Script:

1) For cicles
2) For functions with any return type
3) For functions without return type (void).

So we must just add one line "assert(...)" in any part of Sript, like this:

Same as for EA.

Sergey Eremin
Sergey Eremin | 6 Dec 2015 at 12:30
Alain Verleyen:

My post was to say you are not right saying there is no way to terminate a program immediately. It's to you to find solution for your Assertion project. 

This is absolutely not a bad practice to use global variable in an indicator. Of course if you want to create your self new limitation with such assertion "it is bad practice", you will find a lot of impossible things.

Ok, I understood you. Thanks, you're right.

 

But in my article I mean solutions for Assertions: universally mechamism of stopping MQL4/5 apps in any place of code (include OnInit and cicles). Just add one line in any part and done. Like it works in any Assertions mechanism in many pragramming languages ;)

Yes, your variants correct. But not for my vision of Assertions, because it's not universal solution for any part of any code. 

 

Thank you for your example of EA. 

Alain Verleyen
Alain Verleyen | 6 Dec 2015 at 13:20
Sergey Eremin:

Ok, I understood you. Thanks, you're right.

 

But in my article I mean solutions for Assertions: universally mechamism of stopping MQL4/5 apps in any place of code (include OnInit and cicles). Just add one line in any part and done. Like it works in any Assertions mechanism in many pragramming languages ;)

Yes, your variants correct. But not for my vision of Assertions, because it's not universal solution for any part of any code. 

 

Thank you for your example of EA. 

I know what you want to do, and it's perfectly doable, you just have to generalize to code I provided.

By analysing the calling context in your macro(s), detecting if it's an EA or an indicator, and parsing __FUNCSIG__.

It's up to you to make it an universal mechanism.

Sergey Eremin
Sergey Eremin | 6 Dec 2015 at 13:36
Alain Verleyen:

I know what you want to do, and it's perfectly doable, you just have to generalize to code I provided.

By analysing the calling context in your macro(s), detecting if it's an EA or an indicator, and parsing __FUNCSIG__.

It's up to you to make it an universal mechanism.

Yes, firstly I thought about such things, but eventually I made it as we see in article :)

Thank you for your comments! 

MQL5 for beginners: Anti-vandal protection of graphic objects MQL5 for beginners: Anti-vandal protection of graphic objects
What should your program do, if graphic control panels have been removed or modified by someone else? In this article we will show you how not to have "ownerless" objects on the chart, and how not to lose control over them in cases of renaming or deleting programmatically created objects after the application is deleted.
Error Handling and Logging in MQL5 Error Handling and Logging in MQL5
This article focuses on general issues linked to handling software errors. Furthermore, the logging term is brought up and the examples of logging implementation with MQL5 tools are shown.
Market Theory Market Theory
A logically complete market theory that would cover all types and varieties of markets for goods and services, micro and macro markets like Forex, hasn't been available until now. This article covers the essence of a new market theory based on the profit analysis, reveals patterns of the current price change and the principle of the mechanism operation that allows the price to find its most optimal value by forming a chain of virtual prices that can develop the controlling influence on the actual price. Mechanisms of formation and change of market trends are also identified here.
Evaluation and selection of variables for machine learning models Evaluation and selection of variables for machine learning models
This article focuses on specifics of choice, preconditioning and evaluation of the input variables (predictors) for use in machine learning models. New approaches and opportunities of deep predictor analysis and their influence on possible overfitting of models will be considered. The overall result of using models largely depends on the result of this stage. We will analyze two packages offering new and original approaches to the selection of predictors.