preview
Try/Catch style in MQL5: Bringing Modern Error Management to Algorithmic Trading

Try/Catch style in MQL5: Bringing Modern Error Management to Algorithmic Trading

MetaTrader 5 — Examples |
161 0
Hans Alexander Nolawon Djurberg
Hans Alexander Nolawon Djurberg

Table of contents:


Introduction

MQL5 does not provide native exception handling features such as Try/Catch/Finally blocks or stack traces, and many critical faults like division by zero or out of range array access occur inside the MetaTrader virtual machine, where they cannot be intercepted. Reliable error management in this environment must therefore focus on preventing unsafe operations rather than catching VM-level failures after they occur. Instead, it prevents them through explicit precondition checks in wrappers and controlled propagation of errors. 

This framework introduces a structured, exception-like model for MQL5 built on defensive validation, specified error objects, cooperative flow control, manual stack tracing, and persistent logging. It provides a clear and maintainable way to propagate and diagnose failures. The goal is not to emulate full runtime exceptions, but to provide a predictable infrastructure that keeps Expert Advisors stable as they grow in complexity. This requires using wrappers consistently; unsafe code outside wrappers remains unprotected.

Exceptions are not exceptions at the language level. The model requires discipline: all dangerous operations must go through a guard/wrapper. Any direct array access, division, or API call outside of the wrappers remains unprotected. Only areas where the guard/wrapper is applied correctly are protected.

By enforcing guard checks before dangerous operations and offering a unified mechanism for reporting context, severity, and error type, the system brings modern software engineering practices to MQL5 while remaining fully native, lightweight for production trading systems.

  • Traditional Approach:
//--- Old way - verbose and error-prone
int handle = FileOpen("data.txt", FILE_READ);
if(handle == INVALID_HANDLE)
  {
   int error = GetLastError();
   Print("Error opening file: ", error);
   return;
  }
  
bool result = FileRead(handle, data);
if(!result)
  {
   error = GetLastError();
   Print("Error reading file: ", error);
   FileClose(handle);
   return;
  }

FileClose(handle);

  • Try/Catch Approach (this is a demonstration of the idea):
//--- New way - clean and intuitive
TRY
  {
   int handle = FileOpen("data.txt", FILE_READ);
   if(handle == INVALID_HANDLE)
      THROW(CFileIOException("Cannot open file", "data.txt"));
//--- Or it can also be like this:
   CHECK_VALID_HANDLE(handle);
//--- if invalid handle exception happens, then the control flow is jumped to CATCH block.
//--- The below codes are not executed !

   bool result = FileRead(handle, data);
   if(!result)
      THROW(CFileIOException("Cannot read file", "data.txt"));

   FileClose(handle);
  }
CATCH(CFileIOException, ex)
  {
   Print("File operation failed: ", ex.ToString());
  }
FINALLY
  {
   if(handle != INVALID_HANDLE)
      FileClose(handle);
  }

Framework Overview

This framework provides fully native error management for MQL5. It offers structured error propagation, defensive flow control, and diagnostic tooling without external dependencies. Its architecture is organized into focused modules that each address a specific part of the error pipeline: CException for structured error representation, CResult for result propagation, CExceptionStack for manual stack tracing, CLogger for persistent diagnostics, GuardMacros for validating dangerous operations, and both TryCatchBlock and TRY/CATCH macros for cooperative control flow. Together, these components form a lightweight, maintainable infrastructure.


Core Exception Model Design

CException - Base Exception Error Object

CException is the base error object of the framework, providing a single structured container for all failure information. It encapsulates the message, type, context, timestamp, severity, and numeric code, and all specialized exceptions inherit from it to supply predefined categories and identifiers. This design creates a unified, and log friendly representation of errors that can be safely propagated across system layers and used consistently by guards, TRY/CATCH macros, and diagnostic modules.

  • Structured error model — stores message, type, severity, timestamp, context, and code in one exception object.
  • Typed hierarchy— specialized subclasses (null, bounds, zero‑divide, handle, etc.) predefined category and code.
  • Unified diagnostics — ToString() produces readable log entries for CLogger and stack tracing.
  • Propagation — integrates with CResult and TRY/CATCH macros for predictable flow control.
//+------------------------------------------------------------------+
//| Exception.mqh                                                    |
//| Robust exception object for MQL5                                 |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Custom exception macros                                         |
//+------------------------------------------------------------------+

//--- Severity levels (string-based for flexibility)
#define EX_SEVERITY_INFO     "INFO"
#define EX_SEVERITY_WARNING  "WARNING"
#define EX_SEVERITY_ERROR    "ERROR"
#define EX_SEVERITY_FATAL    "FATAL"


//--- custom exception codes
#define CODE_GENERIC        2000
#define CODE_DATABASE       2001
#define CODE_FILE_IO        2002
#define CODE_NETWORK        2003
#define CODE_MEMORY         2004
#define CODE_VALIDATION     2005
#define CODE_ZERO_DIVIDE    2006
#define CODE_IS_NULL        2007
#define CODE_ARRAY_BOUNDS   2008
#define CODE_INVALID_HANDLE 2009
#define CODE_TRADE_FAIL     2010

//--- defining EXCEPTION macro for detecting the using of
//--- exception handler
#ifndef EXCEPTION
#define EXCEPTION
#endif


//+------------------------------------------------------------------+
//|  Include string array                                            |
//+------------------------------------------------------------------+
#include <Arrays\ArrayString.mqh>

//+------------------------------------------------------------------+
//|  Enum                                                            |
//+------------------------------------------------------------------+
//--- Exception types
enum ENUM_EXCEPTION_TYPE
  {
   EXCEPTION_GENERIC,
   EXCEPTION_DATABASE,
   EXCEPTION_FILE_IO,
   EXCEPTION_NETWORK,
   EXCEPTION_MEMORY,
   EXCEPTION_VALIDATION,

   EXCEPTION_ZERO_DIVIDE,
   EXCEPTION_IS_NULL,
   EXCEPTION_ARRAY_BOUNDS,
   EXCEPTION_INVALID_HANDLE,
   EXCEPTION_TRADE_FAIL
  };

//+------------------------------------------------------------------+
//| CException Base Exception Class                                  |
//+------------------------------------------------------------------+
class CException : public CObject
  {
protected:
   string            m_message;
   ENUM_EXCEPTION_TYPE m_type;
   string            m_context;
   datetime          m_timestamp;
   int               m_error_code;
   string            m_severity;


public:

   //--- Constructors
                     CException()
     {
      m_error_code = 0;
      m_message  = "";
      m_severity = EX_SEVERITY_ERROR;
      m_timestamp = TimeCurrent();
      m_context = "";
      m_type = EXCEPTION_GENERIC;
     }

                     CException(string message, ENUM_EXCEPTION_TYPE type = EXCEPTION_GENERIC,
              string context = "", int error_code = 0, string severity = EX_SEVERITY_ERROR) :
                     m_message(message), m_type(type), m_context(context),
                     m_timestamp(TimeCurrent()), m_error_code(error_code),
                     m_severity(severity) {}


                     CException(const CException& ex)
     {
      m_error_code = ex.m_error_code;
      m_message  = ex.m_message;
      m_severity = ex.m_severity;
      m_timestamp = ex.m_timestamp;
      m_context = ex.m_context;
      m_type = ex.m_type;
     }


   //--- GetMessage
   string            GetMessage() const { return m_message; }

   //--- GetType
   ENUM_EXCEPTION_TYPE GetType() const { return m_type; }

   //--- GetContext
   string            GetContext() const { return m_context; }

   //--- GetTimestamp
   datetime          GetTimestamp() const { return m_timestamp; }

   //--- GetErrorCode
   int               GetErrorCode() const { return m_error_code; }

   //--- Severity
   string            Severity() const { return m_severity; }

   //--- ToString
   string            ToString() const
     {
      string type_str = "";
      switch(m_type)
        {
         case EXCEPTION_GENERIC:
            type_str = "Generic";
            break;
         case EXCEPTION_DATABASE:
            type_str = "Database";
            break;
         case EXCEPTION_FILE_IO:
            type_str = "FileIO";
            break;
         case EXCEPTION_NETWORK:
            type_str = "Network";
            break;
         case EXCEPTION_MEMORY:
            type_str = "Memory";
            break;
         case EXCEPTION_VALIDATION:
            type_str = "Validation";
            break;

         case EXCEPTION_ZERO_DIVIDE:
            type_str = "ZeroDivide";
            break;
         case EXCEPTION_IS_NULL:
            type_str = "IsNull";
            break;
         case EXCEPTION_ARRAY_BOUNDS:
            type_str = "ArrayOutOfRange";
            break;
         case EXCEPTION_INVALID_HANDLE:
            type_str = "InvalidHandle";
            break;
         case EXCEPTION_TRADE_FAIL:
            type_str = "TradeFail";
            break;
        }

      string result = StringFormat("[%s] [%s Exception] %s", m_severity, type_str, m_message);
      if(m_error_code != 0)
         result += StringFormat(" [Error Code: %d]", m_error_code);
      if(m_context != "")
         result += StringFormat(" (Context: %s)", m_context);

      result += StringFormat(" Time=%s", TimeToString(m_timestamp, TIME_DATE | TIME_SECONDS));

      return result;
     }

  };

//+------------------------------------------------------------------+
//|  CDatabaseException                                              |
//+------------------------------------------------------------------+
class CDatabaseException : public CException
  {
public:
   //--- Constructors
                     CDatabaseException():CException() {}
                     CDatabaseException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_DATABASE, context, CODE_DATABASE, severity) {}
  };

//+------------------------------------------------------------------+
//|  CFileIOException                                                |
//+------------------------------------------------------------------+
class CFileIOException : public CException
  {
public:
   //--- Constructors
                     CFileIOException():CException() {}
                     CFileIOException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_FILE_IO, context, CODE_FILE_IO, severity) {}
  };

//+------------------------------------------------------------------+
//|  CValidationException                                            |
//+------------------------------------------------------------------+
class CValidationException : public CException
  {
public:
   //--- Constructors
                     CValidationException():CException() {}
                     CValidationException(string message, string context = "") :
                     CException(message, EXCEPTION_VALIDATION, context, CODE_VALIDATION) {}
  };

//+------------------------------------------------------------------+
//|   CZeroDivideException                                           |
//+------------------------------------------------------------------+
class CZeroDivideException : public CException
  {
public:
   //--- Constructors
                     CZeroDivideException():CException() {}
                     CZeroDivideException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_ZERO_DIVIDE, context, CODE_ZERO_DIVIDE, severity) {}
  };

//+------------------------------------------------------------------+
//|  CArrayBoundsException                                           |
//+------------------------------------------------------------------+
class CArrayBoundsException : public CException
  {
public:
   //--- Constructors
                     CArrayBoundsException():CException() {}
                     CArrayBoundsException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_ARRAY_BOUNDS, context, CODE_ARRAY_BOUNDS, severity) {}
  };

//+------------------------------------------------------------------+
//|  CInvalidHandleException                                         |
//+------------------------------------------------------------------+
class CInvalidHandleException : public CException
  {
public:
   //--- Constructors
                     CInvalidHandleException():CException() {}
                     CInvalidHandleException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_INVALID_HANDLE, context, CODE_INVALID_HANDLE, severity) {}
  };

//+------------------------------------------------------------------+
//|  CIsNullException                                                |
//+------------------------------------------------------------------+
class CIsNullException : public CException
  {
public:
   //--- Constructors
                     CIsNullException():CException() {}
                     CIsNullException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_IS_NULL, context, CODE_IS_NULL, severity) {}
  };

//+------------------------------------------------------------------+
//|  CTradeFailException                                             |
//+------------------------------------------------------------------+
class CTradeFailException : public CException
  {
public:
   //--- Constructors
                     CTradeFailException():CException() {}
                     CTradeFailException(string message, string context = "", string severity = EX_SEVERITY_ERROR) :
                     CException(message, EXCEPTION_TRADE_FAIL, context, CODE_TRADE_FAIL, severity) {}
  };

Exception.mqh

CResult - Exception Result Pattern

CResult<T> is the framework’s second safety layer: a compact, dual state return type that replaces fragile bool checks and GetLastError() with a structured, predictable result object. It allows functions to report either a successful value or a failure described by a CException, without terminating execution. This mechanism enables clean flow control, branching, and consistent recovery patterns—especially important in MQL5, where true runtime exceptions cannot be intercepted and dangerous operations must be validated before execution.

  • Dual state result — success carries a value of type T; failure carries a CException.
  • Predictable flow control — IsOk() and IsError() provide branching.
  • Consistent API — Ok() and Fail() factory methods standardize return statements across the framework.
  • API accessors — TryGetValue() retrieves the result if it is Ok; TryGetError() exposes the exception for logging or propagation.
//+------------------------------------------------------------------+
//| ExceptionResult.mqh                                              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include file                                                    |
//+------------------------------------------------------------------+
#include <ExceptionFramework\Exception.mqh>

//+------------------------------------------------------------------+
//|  CResult - Result checker                                        |
//+------------------------------------------------------------------+
template<typename T>
class CResult
  {
private:
   bool              m_is_ok;
   T                 m_value;
   CException        m_exception;

public:
                     CResult()
      :              m_is_ok(false)
     {
     }

   //--- Success
                     CResult(const T value)
     {
      m_is_ok    = true;
      m_value    = value;
     }

   //--- Failure
                     CResult(const CException &ex)
     {
      m_is_ok     = false;
      m_exception = ex;
     }

                     CResult(const CResult &other)
     {
      m_is_ok     = other.m_is_ok;
      m_value     = other.m_value;
      m_exception = other.m_exception;
     }

   CResult          operator=(const CResult &other)
     {
      if(&this != &other)
        {
         m_is_ok     = other.m_is_ok;
         m_value     = other.m_value;
         m_exception = other.m_exception;
        }

      return this;
     }

   //--- State
   bool              IsOk() const
     {
      return m_is_ok;
     }

   bool              IsError() const
     {
      return !m_is_ok;
     }

   //--- OK
   static CResult<T>* Ok(const T value)
     {
      return new CResult<T>(value);
     }

   //--- Fail
   static CResult<T>* Fail(const CException &ex)
     {
      return new CResult<T>(ex);
     }

   //--- Safe value access
   bool              TryGetValue(T &value) const
     {
      if(!m_is_ok)
         return false;

      value = m_value;
      return true;
     }

   //--- Safe error access
   bool              TryGetError(CException &ex) const
     {
      if(m_is_ok)
         return false;

      ex = m_exception;
      return true;
     }
  };

ExceptionResult.mqh

  • Simple usage example(this is the basic implementation):
CResult<double>* result = CResult<double>::Ok(1.234);

double value;
if(result.TryGetValue(value))
   Print("Returned value : " + (string)value);
delete result;

  • Or(this is also the basic implementation):

CZeroDivideException ex("Division by zero", "CalculateRisk()", EX_SEVERITY_FATAL);
CResult<double>* result = CResult<double>::Fail(ex);

if(result.IsError())
  {
   CException ex;

   if(result.TryGetError(ex))
      Print(ex.ToString());
  }
delete result;

CExceptionStack - Manual Stack Trace

CExceptionStack implements a manual call stack. It provides a deterministic, readable stack trace in MQL5, which has no native stack introspection. Each section must explicitly call Push() when entering and Pop() when leaving; this manual discipline is essential, because forgetting a Pop() produces an incorrect or misleading trace. The module records human readable frames, enforces a depth limit to prevent runaway recursion, and generates a clear ordered traceback via Trace(), integrating tightly with CException and CLogger for structured diagnostics in large EAs.

  • Explicit Push/Pop frames — every block must add and remove context manually; missing a Pop() corrupts the trace.
  • Deterministic depth control — prevents recursion overflow and ensures stable long running EA behavior.
  • Readable traceback — Trace() prints a clean, ordered execution path for debugging and logging.
  • Integrated diagnostics — works with CException and CLogger to produce consistent, structured error reports.
//+------------------------------------------------------------------+
//| ExceptionStack.mqh                                               |
//| Manual call stack tracing for MQL5                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Macro                                                           |
//+------------------------------------------------------------------+
//--- Maximum stack depth (prevent runaway growth)
#define EX_STACK_MAX_DEPTH 64

//+------------------------------------------------------------------+
//| CExceptionStack                                                  |
//+------------------------------------------------------------------+
class CExceptionStack
  {
private:
   string            m_stack[];
   int               m_depth;

public:
   //--- Constructor
                     CExceptionStack()
     {
      m_depth = 0;
      ArrayResize(m_stack, 0);
     }

   //--- Push a new context onto the stack
   bool              Push(const string context)
     {
      if(m_depth >= EX_STACK_MAX_DEPTH)
         return false;

      ArrayResize(m_stack, m_depth + 1);
      m_stack[m_depth] = context;
      m_depth++;
      return true;
     }

   //--- Pop the last context
   bool              Pop()
     {
      if(m_depth <= 0)
         return false;

      m_depth--;
      ArrayResize(m_stack, m_depth);
      return true;
     }

   //--- Clear the stack
   void              Clear()
     {
      m_depth = 0;
      ArrayResize(m_stack, 0);
     }

   //--- Current depth
   int               Depth() const
     {
      return m_depth;
     }

   //--- Build a readable stack trace
   string            Trace() const
     {
      if(m_depth == 0)
         return "<empty stack>";

      string trace = "";
      for(int i = m_depth - 1; i >= 0; i--)
        {
         trace += StringFormat("#%d  %s\n", m_depth - i, m_stack[i]);
        }
      return trace;
     }
  };

ExceptionStack.mqh

  • Sample usage(this is a simplified example):
//+------------------------------------------------------------------+
//|  Context Guard Macros                                            |
//+------------------------------------------------------------------+
#define EX_STACK_PUSH(stack, ctx) stack.Push(ctx)
#define EX_STACK_POP(stack) stack.Pop()

//+------------------------------------------------------------------+
//|  Standard usage in functions                                     |
//+------------------------------------------------------------------+
CResult<double>* CalculateLot(CExceptionStack &stack)
  {
   EX_STACK_PUSH(stack, "CalculateLot()");

//--- ... code logic ...
   Print(stack.Trace());
   EX_STACK_POP(stack);
   return CResult<double>::Ok(0.1);
  }

//+------------------------------------------------------------------+
//|  OnTick                                                          |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   CExceptionStack stack;
   EX_STACK_PUSH(stack, "OnTick()");

//--- Combination of Stack + Exception
   CResult<double>* result = CalculateLot(stack);
   Print(stack.Trade());
   EX_STACK_POP(stack);

   if(result.IsError())
     {
      CException ex;
      if(result.TryGetError(ex))
         {
          Print(ex.ToString());
         }
     }
    else
     {
      double value;
      if(result.TryGetValue(value))
        Print("value : "+ (string)value);
     }
    delete result;
  }
  • Sample output:

#2 CalculateLot()
#1 OnTick()
value : 0.1

CLogger - Logging System Integrated with Exceptions

CLogger is a lightweight diagnostic component. It persists routine messages and structured exceptions in a unified, readable format. It opens a single file handle, prints every entry to the terminal, and writes it to disk, applying level based filtering for normal logs. Because logging in large EAs cannot rely solely on terminal output, CLogger provides durable post-mortem visibility into failures, including message, context, timestamp, severity, and optional stack traces. Its design is deterministic, minimal, and resilient: if the file cannot be opened, it gracefully falls back to terminal‑only logging, if printText flag is true. It is false by default.
  • Dual output — prints to terminal and writes to file simultaneously.
  • Level filtering — INFO, WARNING, ERROR, and FATAL with independent exception logging.
  • Single file handle — opened once and reused for all writes.
  • Exception integration — logs fully formatted CException objects with optional stack traces.
//+------------------------------------------------------------------+
//| Logger.mqh                                                       |
//| Professional logging system for MQL5                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include file                                                    |
//+------------------------------------------------------------------+
#include <ExceptionFramework\Exception.mqh>

//+------------------------------------------------------------------+
//|  Macros                                                          |
//+------------------------------------------------------------------+
//--- Log levels                                                     
#define LOG_LEVEL_INFO     0
#define LOG_LEVEL_WARNING  1
#define LOG_LEVEL_ERROR    2
#define LOG_LEVEL_FATAL    3

//--- Default log file name                                           
#define DEFAULT_LOG_FILE   "ExceptionFramework.log"

//+------------------------------------------------------------------+
//| CLogger                                                          |
//+------------------------------------------------------------------+
class CLogger
  {
private:
   int               m_minLevel;
   string            m_fileName;
   int               m_fileHandle;
   bool              m_writeFile;
   bool              m_printText;

   //--- Convert log level to string                                     
   string            LevelToString(const int level) const
     {
      switch(level)
        {
         case LOG_LEVEL_INFO:
            return "INFO";
         case LOG_LEVEL_WARNING:
            return "WARNING";
         case LOG_LEVEL_ERROR:
            return "ERROR";
         case LOG_LEVEL_FATAL:
            return "FATAL";
        }
      return "UNKNOWN";
     }

   //--- Write line to file                                              
   void              WriteLine(const string line)
     {
      if(m_fileHandle == INVALID_HANDLE)
         return;

      FileWrite(m_fileHandle, line);
      FileFlush(m_fileHandle);
     }
     
     // Print to terminal
   void              print(const string line)
     {
      if(!m_printText)
         return;

      Print(line);
     } 

public:
   //--- Constructor                                                     
                     CLogger(
      const string fileName = DEFAULT_LOG_FILE,
      const int    minLevel = LOG_LEVEL_INFO,
      const bool   writeFile = true,
      const bool   printText = false
   )
     {
      m_fileName = fileName;
      m_minLevel = minLevel;
      m_writeFile = writeFile;
      m_printText = printText;
      m_fileHandle = INVALID_HANDLE;

      if(!m_writeFile)
        {
         m_fileHandle = FileOpen(
                           m_fileName,FILE_READ |
                           FILE_WRITE | FILE_TXT | FILE_COMMON | FILE_ANSI
                        );

         if(m_fileHandle == INVALID_HANDLE)
           {
            print("Logger: failed to open log file");
           }
        }
     }

   //--- Destructor                                                      
                    ~CLogger()
     {
      if(m_fileHandle != INVALID_HANDLE)
         FileClose(m_fileHandle);
     }

   //--- Generic log                                                     
   void              Log(const int level, const string message)
     {
      if(level < m_minLevel)
         return;

      string line = StringFormat(
                       "[%s] %s | %s",
                       LevelToString(level),
                       TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS),
                       message
                    );

      print(line);
      WriteLine(line);
     }

   //--- Convenience methods                                             

   void              Info(const string message)
     {
      Log(LOG_LEVEL_INFO, message);
     }

   void              Warning(const string message)
     {
      Log(LOG_LEVEL_WARNING, message);
     }

   void              Error(const string message)
     {
      Log(LOG_LEVEL_ERROR, message);
     }

   void              Fatal(const string message)
     {
      Log(LOG_LEVEL_FATAL, message);
     }

   //--- Log exception with optional stack trace                         
   void              LogException(
      const CException &ex,
      const string stackTrace = ""
   )
     {
      if(level < m_minLevel)
         return;

      string line = ex.ToString();

      print(line);
      WriteLine(line);

      if(stackTrace != "")
        {
         print(stackTrace);
         WriteLine(stackTrace);
        }
     }
  };

Logger.mqh

  • Real usage example(this is the basic implementation):
CLogger logger("MyEA.log", LOG_LEVEL_INFO);
logger.Info("EA started");

if(result.IsError())
  {
   CException ex;
   if(result.TryGetError(ex))
      logger.LogException(ex, stack.Trace());
  }

  • Sample output:
[FATAL] [ZeroDivide Exception] Risk value is zero (division by zero prevented) [Error Code: 2006] (Context: CalculateLot) Time=2026.08.12 12:08:47
#1 OnTick()
#2 CalculateLot()

CTryCatchBlock - OOP Try/Catch controller

CTryCatchBlock is the core engine of the framework’s OOP style exception model, providing structured TRY/CATCH behavior. It maintains active block TRY exception state, allowing exceptions raised through OOP calls or macro‑based THROW operations to be captured uniformly. The class exposes a small set of methods that define its behavior: Try() adds/registers active exception state, Throw() / _throw() store the thrown exception, Catch<T>() matches specific exception types, Catch_Any() handles any failure, and Finally() unregisters the active TRY context.

//+------------------------------------------------------------------+
//| TryCatchBlock.mqh                                                |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\Exception.mqh>
#include <ExceptionFramework\ExceptionStack.mqh>

//+------------------------------------------------------------------+
//| CTryCatchBlock                                                   |
//+------------------------------------------------------------------+
class CTryCatchBlock
  {
private:
   CException*       m_caught_exception;
   bool              m_has_exception;
   string            m_block_name;

   CExceptionStack   m_exception_stack;

   static CTryCatchBlock* s_active_blocks[64];
   static int             s_active_count;

   //--- register this context
   bool              RegisterContext()
     {
      if(IsRegistered())
         return(true);

      if(s_active_count >= 64)
         return(false);

      s_active_blocks[s_active_count] = &this;
      s_active_count++;

      return(true);
     }

   //--- unregister this exact context
   bool              UnregisterContext()
     {
      int index = -1;

      for(int i=0; i<s_active_count; i++)
        {
         if(s_active_blocks[i] == &this)
           {
            index = i;
            break;
           }
        }

      if(index < 0)
         return(false);

      for(int i=index; i<s_active_count-1; i++)
         s_active_blocks[i] = s_active_blocks[i+1];

      s_active_blocks[s_active_count-1] = NULL;
      s_active_count--;

      return(true);
     }

   //--- check whether this context is registered
   bool              IsRegistered() const
     {
      for(int i=0; i<s_active_count; i++)
        {
         if(s_active_blocks[i] == &this)
            return(true);
        }

      return(false);
     }

   //--- get current active context
   static CTryCatchBlock* CurrentContext()
     {
      if(s_active_count <= 0)
         return(NULL);

      return(s_active_blocks[s_active_count-1]);
     }

public:
   //--- constructor
                     CTryCatchBlock(string block_name = "") :
                     m_caught_exception(NULL),
                     m_has_exception(false),
                     m_block_name(block_name)
     {
     }

   //--- destructor
                    ~CTryCatchBlock()
     {
      UnregisterContext();

      if(m_caught_exception != NULL)
        {
         delete m_caught_exception;
         m_caught_exception = NULL;
        }

      m_has_exception = false;
     }

   //--- initialize TRY context
   bool              Try()
     {
      UnregisterContext();

      if(m_caught_exception != NULL)
        {
         delete m_caught_exception;
         m_caught_exception = NULL;
        }

      m_has_exception = false;

      return(RegisterContext());
     }

   //--- Catch specific exception type
   template<typename T>
   bool              Catch(T &exception,CException* caught_exception)
     {
      if(caught_exception == NULL)
         return(false);

      if(!m_has_exception)
         return(false);

      T* typed_exception = dynamic_cast<T*>(caught_exception);

      if(typed_exception == NULL)
         return(false);

      exception = typed_exception;

      m_has_exception = false;

      return(true);
     }

   //--- Generic catch
   bool              CatchAny(CException *exception)
     {
      if(!m_has_exception)
         return(false);

      if(m_caught_exception == NULL)
         return(false);

      exception = m_caught_exception;

      return(true);
     }

   //--- Throw into this specific context
   void              Throw(CException *exception)
     {
      if(m_caught_exception != NULL)
        {
         delete m_caught_exception;
         m_caught_exception = NULL;
        }

      m_caught_exception = exception;
      m_has_exception = (exception != NULL);
     }

   //--- Static throw used by number<T>
   static void       _throw(CException *exception)
     {
      CTryCatchBlock* current = CurrentContext();

      if(current == NULL)
        {
         if(exception != NULL)
            delete exception;

         return;
        }

      current.Throw(exception);
     }

   //--- Check local exception state
   bool              HasException() const
     {
      return(m_has_exception);
     }

   //--- Check current active context exception state
   static bool       _hasException()
     {
      CTryCatchBlock* current = CurrentContext();

      if(current == NULL)
         return(false);

      return(current.m_has_exception);
     }

   //--- Get local exception
   CException*       GetException() const
     {
      return(m_caught_exception);
     }

   //--- Get current active context exception
   static CException* _getException()
     {
      CTryCatchBlock* current = CurrentContext();

      if(current == NULL)
         return(NULL);

      return(current.m_caught_exception);
     }

   //--- Rethrow current exception to parent context
   void              Rethrow()
     {
      if(!m_has_exception || m_caught_exception == NULL)
         return;

      CException* exception = m_caught_exception;

      m_caught_exception = NULL;
      m_has_exception = false;

      UnregisterContext();

      CTryCatchBlock* parent = CurrentContext();

      if(parent != NULL)
        {
         parent.Throw(exception);
         return;
        }

      delete exception;
     }

   //--- Push execution context into local stack
   void              PushStack(string context)
     {
      m_exception_stack.Push(context);
     }

   //--- Pop execution context from local stack
   void              PopStack()
     {
      m_exception_stack.Pop();
     }

   //--- Get local execution stack trace
   string            TraceStack()
     {
      return(m_exception_stack.Trace());
     }

   //--- Finish this TRY context
   void              Finally()
     {
      UnregisterContext();

      m_has_exception = false;
     }

   //--- Return block name
   string            BlockName() const
     {
      return(m_block_name);
     }

   //--- Number of active TRY contexts
   static int        ActiveContextCount()
     {
      return(s_active_count);
     }

   //--- Get active context by index
   static CTryCatchBlock* ActiveContext(const int index)
     {
      if(index < 0 || index >= s_active_count)
         return(NULL);

      return(s_active_blocks[index]);
     }
  };

//+------------------------------------------------------------------+
//| Static members                                                   |
//+------------------------------------------------------------------+
CTryCatchBlock* CTryCatchBlock::s_active_blocks[64];
int             CTryCatchBlock::s_active_count = 0;

TryCatchBlock.mqh



Defensive Guards and TRY/CATCH Macros

GUARD_* for CResult<bool>* functions

The GUARD_* macros implement preventive safety inside functions that return CResult<bool>* object. Instead of allowing dangerous operations to reach the MQL5 VM, each guard checks conditions such as zero‑divide, null object, array-bounds, or invalid handles, and immediately returns Fail(CException) when a violation is detected. This converts potential errors into structured exceptions and ensures deterministic flow control.

  • Preventive validation — checks unsafe conditions before execution.
  • CResult integration — failures return Fail(...) with a typed exception.
  • Error avoidance — replaces errors with controlled exception objects.
  • Coverage — zero‑divide, null, bounds, invalid handle, trade failures.

CHECK_* for TRY { } blocks

The CHECK_* macros operate inside TRY/CATCH blocks. Unlike GUARD_* macros, they do not return a CResult object; instead, they activate an exception flag inside CTryCatchBlock, triggering a controlled jump to the corresponding CATCH section by break operator. They provide structured flow control throw, but implemented through macros and cooperative logic.

  • Exception signaling — triggers _throw(...) inside TRY.
  • Flow transfer — breaks execution to CATCH block (for loops(for/while)needs THROWNclause after it).
  • Preventive model — validates operations before the VM executes them. the protections forzero-divide&array-boundsare only valid with disciplined use ofnumber<T>&array<T>wrappers.
//+------------------------------------------------------------------+
//| GuardMacros.mqh                                                  |
//| Defensive programming guards for MQL5                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\Exception.mqh>
#include <ExceptionFramework\ExceptionResult.mqh>
#include <ExceptionFramework\TryCatchBlock.mqh>


//+------------------------------------------------------------------+
//|  Guard macros                                                    |
//+------------------------------------------------------------------+

//--- Zero division guard                                             
#define GUARD_ZERO_DIVIDE(denominator, context)                    \
   if((denominator) == 0.0)                                        \
   {                                                               \
      return CResult<bool>::Fail(                                  \
         CZeroDivideException(                                     \
            "Division by zero",                                    \
            context,                                               \
            EX_SEVERITY_FATAL                                      \
         )                                                         \
      );                                                           \
   }

//--- Zero division check                                             
#define CHECK_ZERO_DIVIDE(expr)                                    \
   expr;                                                           \
   if(CTryCatchBlock::_hasException())                             \
   {                                                               \
      break;                                                       \
   }

//--- Null object / pointer guard                                     
#define GUARD_NOT_NULL(ptr, context)                               \
   if((ptr) == NULL)                                               \
   {                                                               \
      return CResult<bool>::Fail(                                  \
         CIsNullException(                                         \
            "Object is null",                                      \
            context,                                               \
            EX_SEVERITY_FATAL                                      \
         )                                                         \
      );                                                           \
   }

//--- Null object / pointer check                                     
#define CHECK_NOT_NULL(ptr)                                        \
  if((ptr) == NULL)                                                \
   {                                                               \
      CTryCatchBlock::_throw(new CIsNullException("null exception",__FUNCTION__,EX_SEVERITY_FATAL)); \
      break;                                                       \
   }

//--- Array bounds guard                                              
#define GUARD_ARRAY_BOUNDS(arr, index, context)                    \
   if((index) < 0 || (index) >= ArraySize(arr))                    \
   {                                                               \
      return CResult<bool>::Fail(                                  \
         CArrayBoundsException(                                    \
            "Array index out of range",                            \
            context,                                               \
            EX_SEVERITY_ERROR                                      \
         )                                                         \
      );                                                           \
   }

//--- Array bounds check                                              
#define CHECK_ARRAY_BOUNDS(expr)                                   \
   expr;                                                           \
   if(CTryCatchBlock::_hasException())                             \
   {                                                               \
      break;                                                       \
   }

//--- Indicator / resource handle guard                               
#define GUARD_VALID_HANDLE(handle, context)                        \
   if((handle) == INVALID_HANDLE)                                  \
   {                                                               \
      return CResult<bool>::Fail(                                  \
         CInvalidHandleException(                                  \
            "Invalid handle",                                      \
            context,                                               \
            EX_SEVERITY_ERROR                                      \
         )                                                         \
      );                                                           \
   }

//--- Indicator / resource handle check                               
#define CHECK_VALID_HANDLE(handle)                                 \
   if((handle) == INVALID_HANDLE)                                  \
   {                                                               \
      CTryCatchBlock::_throw(new CInvalidHandleException("invalid handle exception",__FUNCTION__,EX_SEVERITY_FATAL)); \
      break;                                                       \
   }

//--- Trade operation guard (uses GetLastError)                       
#define GUARD_TRADE_SUCCESS(success, context)                      \
   if(!(success))                                                  \
   {                                                               \
      return CResult<bool>::Fail(                                  \
         CTradeFailException(                                      \
            "Trade operation failed",                              \
            context,                                               \
            EX_SEVERITY_ERROR                                      \
         )                                                         \
      );                                                           \
   }

//--- Context Guard Macros                                            
#define EX_STACK_PUSH(stack, ctx)  stack.Push(ctx)
#define EX_STACK_POP(stack)        stack.Pop()

GuardMacros.mqh

  • Real usage examples(this is simplified examples):

//--- Zero divide
CResult<bool>* CalculateRisk(double balance, double risk)
{
   GUARD_ZERO_DIVIDE(risk, "CalculateRisk()");
   return CResult<bool>::Ok(true);
}

//--- Array out of range
CResult<bool>* ReadValue(double &arr[], int index)
{
   GUARD_ARRAY_BOUNDS(arr, index, "ReadValue()");
   double x = arr[index];
   return CResult<bool>::Ok(true);
}

//--- Null object
CResult<bool>* UseObject(CObject *obj)
{
   GUARD_NOT_NULL(obj, "UseObject()");
   return CResult<bool>::Ok(true);
}

//--- Invalid handle
CResult<bool>* InitIndicator(int handle)
{
   GUARD_VALID_HANDLE(handle, "InitIndicator()");
   return CResult<bool>::Ok(true);
}

number<T> and array<T> wrappers

The number<T> and array<T> wrappers extend MQL5 with zero-divide and array-access checking. All operations pass through overloaded operators, allowing the framework to detect dangerous conditions before the VM triggers fatal errors.

  • Zero‑divide protection — operator/ checks divisor and throws CZeroDivideException flag. after catching the exception, the value returned must be checked by HasError() method.
  • Bounds checking — GET/SET wrappers type cast. operator[] checks array access and throws CArrayBoundsException flag. if valid, returns number<T> object.
  • Usage — being used through CHECK_ZERO_DIVIDE and CHECK_ARRAY_BOUNDS guards only.
They require special CHECK_*, GET/SET, manual HasError() checking, and do not cover all the syntax/semantics of regular numbers and arrays.
//+------------------------------------------------------------------+
//| number.mqh                                                       |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\TryCatchBlock.mqh>


#ifdef EXCEPTION

//+------------------------------------------------------------------+
//| executes a number                                                |
//+------------------------------------------------------------------+
template<typename T>
class number
  {
private:
   T                 _value;
   bool              _has_error;

public:
   //--- Constructors
                     number(): _value(0),_has_error(false) {}
                     number(const T &v): _value(v),_has_error(false) {}
                     number(T v): _value(v),_has_error(false) {}
                     number(const number<T> &n): _value(n._value),_has_error(n._has_error) {}

   //--- Assignment operators
   T                 operator=(T v)
     {
      if(_has_error)
         return 0;

      _value = v;
      return _value;
     }
   number<T>         operator=(const number<T> &n)
     {
      _value = n._value;
      _has_error = n._has_error;
      return this;
     }

   //--- has error
   bool              HasError() const
     {
      return _has_error;
     }
   void              SetError()
     {
      _has_error = true;
     }

   //--- returns its value
   T                 Value() const
     {
      return _value;
     }

   //--- Arithmetic operators
   T                 operator+(const number<T> &n) const
     {
      return _value + n._value;
     }
   T                 operator-(const number<T> &n) const
     {
      return _value - n._value;
     }
  
   number<T>         operator/(number<T> &n)
     {
      number<T> result;

      if(n._value == 0)
        {
         result._has_error = true;

         CTryCatchBlock::_throw(
            new CZeroDivideException(
               "division by zero",
               __FUNCTION__,
               EX_SEVERITY_FATAL
            )
         );

         return result;
        }

      result._value = _value / n._value;

      return result;
     }

   T                 operator*(number<T> &n) const
     {
      return _value * n._value;
     }

   //--- Boolean operators
   bool              operator==(T v) const
     {
      return (_value == v);
     }
   bool              operator!=(T v) const
     {
      return !(_value == v);
     }
   bool              operator<(number<T> &n) const
     {
      return _value < n._value;
     }
   bool              operator>(number<T> &n) const
     {
      return _value > n._value;
     }
   bool              operator<=(number<T> &n) const
     {
      return _value <= n._value;
     }
   bool              operator>=(number<T> &n) const
     {
      return _value >= n._value;
     }

   //--- Compact arithmetic operators
   T                 operator+=(const number<T> &n)
     {
      return _value += n._value;
     }
   T                 operator-=(const number<T> &n)
     {
      return _value -= n._value;
     }
   T                 operator*=(const number<T> &n)
     {
      return _value *= n._value;
     }

   number<T>         operator/=(const number<T> &n)
     {
      number<T> result;

      if(n._value == 0)
        {
         result._has_error = true;

         CTryCatchBlock::_throw(
            new CZeroDivideException(
               "division by zero",
               __FUNCTION__,
               EX_SEVERITY_FATAL
            )
         );

         return result;
        }

      result._value = _value / n._value;

      return result;
     }
   T                 operator++()
     {
      return ++_value;
     }
   T                 operator--()
     {
      return --_value;
     }
  };
#endif

number.mqh

//+------------------------------------------------------------------+
//| array.mqh                                                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\TryCatchBlock.mqh>

//+------------------------------------------------------------------+
//|  Getter/Setter Type cast macros                                  |
//+------------------------------------------------------------------+
#define GET(indx) ((int)indx)
#define SET(indx) ((uint)indx)


#ifdef EXCEPTION

//+------------------------------------------------------------------+
//| executes an array                                                |
//+------------------------------------------------------------------+
template <typename T>
class array
  {

private:
   int               index;

protected:
   T                 data[];

public:
   //--- Constructor
                     array():index(0)
     {
     }

   //--- Destructor
                    ~array()
     {
     }

   //--- Indexer operators
   number<T>         operator[](int i) const
     {
      number<T> result;

      if(i < 0 || i >= ArraySize(data))
        {
         result.SetError();

         CTryCatchBlock::_throw(
            new CArrayBoundsException(
               "index out of bounds",
               __FUNCTION__,
               EX_SEVERITY_FATAL
            )
         );

         return result;
        }

      result = data[i];

      return result;
     }

   array            operator[](uint i)
     {
      if(int(i) < 0 || int(i) >= ArraySize(data))
        {
         CTryCatchBlock::_throw(new CArrayBoundsException("index out of bounds",__FUNCTION__,EX_SEVERITY_FATAL));
         return this;
        }
      index=int(i);
      return this;
     }

   //--- Assignment operators
   void              operator=(const T v)
     {
      if(!CTryCatchBlock::_hasException())
        {
         data[index]=v;
        }
     }

   void              operator=(const array &a)
     {
      int i, n = a.size();
      ArrayResize(data, n);
      for(i = 0; i < n; i++)
        {
         data[i] = a[GET(i)].Value();
        }
     }

   //--- Adding to array operator
   array*            operator+(const array &a)
     {
      int n = a.size();
      for(int i = 0; i < n; i++)
        {
         add(a[GET(i)].Value());
        }
      return &this;
     }

   //--- Size of array index
   int               size() const
     {
      return ArraySize(data);
     }

   //--- Updating a value index
   void              update(int i, T value)
     {
      if(i < 0 || i >= ArraySize(data))
        {
         CTryCatchBlock::_throw(new CArrayBoundsException("index out of bounds",__FUNCTION__,EX_SEVERITY_FATAL));
         return;
        }
      data[i] = value;
     }

   //--- Adding to array
   void              add(T v)
     {
      int n = ArraySize(data);
      ArrayResize(data, n + 1);
      data[n] = v;
     }

   //--- Resizing the array
   void              resize(int n)
     {
      ArrayResize(data, n);
     }

  };
//+------------------------------------------------------------------+
#endif

array.mqh

Try/Catch/Finally macro models(Functional & OOP styles)

The framework simulates structured TRY/CATCH using macros built on  do {... } while(false) and break. A THROW stops execution inside TRY, marks the block as failed, and breaks control to CATCH clause. Because the model is macro-based, TRY blocks must wrap a full {... } scope. For loops (for/while), THROWN must be placed carefully to ensure correct propagation. THROW exits only the loop; THROWN must follow the loop. In functional style, THROW uses an exception object. In OOP style, THROW uses a CException* created with new. FINALLY is the closing block of the TRY, it is mandatory at the end of each TRY/CATCH block for both styles. for OOP style FINALLY is to unregister the last activated TRY. both of them support nested TRY blocks. EX_LOGGER is shared because it provides logging only; it does not store exception state or TRY/CATCH context. Each CTryCatchBlock instance owns its exception object, flag, and stack, so nested blocks do not overwrite each other's state.

Limitation: number and array checksvia CHECK_ZERO_DIVIDE and CHECK_ARRAY_BOUNDS are only active in OOP style.

The behavior depends on the correct placement of {}, THROWN, break, the order of CATCH, and the form of THROW. An error in the block structure can change the flow of execution without an obvious design error.

  • Functional style:
//+------------------------------------------------------------------+
//| TryCatchFuncMacros.mqh                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\ExceptionStack.mqh>
#include <ExceptionFramework\Logger.mqh>
#include <ExceptionFramework\ExceptionResult.mqh>

//+------------------------------------------------------------------+
//|  TRY / CATCH / FINALLY  functionality macros                     |
//|  Macro for simplified try-catch usage                            |
//+------------------------------------------------------------------+

//--- TRY macro
#define TRY                                                                                   \
{                                                                                             \
   CResult<bool>* __try_result=NULL;                                                          \
   bool __has_error=false;                                                                    \
   CException __exception;                                                                    \
   CExceptionStack __try_stack;                                                               \
   __try_stack.Push("File: " + __FILE__ + ", Line: " + (string)__LINE__ + ", " + __FUNCTION__); \
   do

//--- THROW macro
#define THROW(ex)                         \
{                                         \
   __try_result=CResult<bool>::Fail(ex);  \
   __exception=ex;                        \
   __has_error=true;                      \
   break;                                 \
}

//--- THROWN macro
#define THROWN                            \
{                                         \
   if(__has_error)                        \
      break;                              \
}

//--- CATCH macro
#define CATCH(ex)                         \
   while(false);                          \
   if(__has_error)                        \
   {                                      \
      CException ex=__exception;

//--- FINALLY macro
#define FINALLY                                      \
   }                                                 \
   if(CheckPointer(__try_result)!=POINTER_INVALID)   \
      delete __try_result;                           \
   __try_stack.Pop();                                \
   if(__has_error)                                   \
      EX_LOGGER.LogException(__exception,            \
                             __try_stack.Trace());   \
}

//--- Global shared logger
static CLogger EX_LOGGER(MQLInfoString(MQL_PROGRAM_NAME)+".log");

TryCatchFuncMacros.mqh

  • OOP style:
//+------------------------------------------------------------------+
//| TryCatchOOPMacros.mqh                                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+

#include <ExceptionFramework\ExceptionStack.mqh>
#include <ExceptionFramework\Logger.mqh>
#include <ExceptionFramework\ExceptionResult.mqh>
#include <ExceptionFramework\TryCatchBlock.mqh>

//+------------------------------------------------------------------+
//|  TRY / CATCH / FINALLY  OOP macros                               |
//|  Macro for simplified try-catch usage                            |
//+------------------------------------------------------------------+

//--- TRY macro
#define TRY \
{ \
   CTryCatchBlock __try_catch_block("File: " + __FILE__ + ", Line: " + (string)__LINE__ + ", " + __FUNCTION__); \
   __try_catch_block.Try(); \
   __try_catch_block.PushStack("File: " + __FILE__ + ", Line: " + (string)__LINE__ + ", " + __FUNCTION__); \
   do


//--- CATCH macro for specified exception object
#define CATCH(exception_type, exception_var) \
   while(false); \
   CException* exception_var = __try_catch_block.GetException(); \
   exception_type _ex_##exception_var; \
   if(__try_catch_block.Catch(_ex_##exception_var, exception_var))


//--- CATCH_ANY macro for any exception object
#define CATCH_ANY(exception_var) \
   while(false); \
   CException* exception_var = __try_catch_block.GetException(); \
   if(__try_catch_block.CatchAny(exception_var))


//--- FINALLY macro for finalizing some resource or
//--- closing the TRY block
#define FINALLY \
   __try_catch_block.PopStack(); \
   if(__try_catch_block.HasException()) \
      EX_LOGGER.LogException(__try_catch_block.GetException(), __try_catch_block.TraceStack()); \
   __try_catch_block.Finally(); \
}


//--- THROW macro for throwing an exception
#define THROW(exception) \
{ \
   __try_catch_block.Throw(exception); \
   break; \
}


//--- THROWN macro for rethrow the control flow to CATCH block after
//--- each for\while loops,it is mandatory
#define THROWN \
{ \
   if(__try_catch_block.HasException()) \
      break; \
}


//--- Global logger
CLogger EX_LOGGER(MQLInfoString(MQL_PROGRAM_NAME) + ".log");

TryCatchOOPMacros.mqh



Practical Examples

All execution examples follow the same pattern: a dangerous operation is validated by a CHECK_* macro or a wrapper, and if the condition is invalid (zero‑divide, array‑bounds, null‑pointer, or invalid‑handle) the wrapper or guard triggers a specified exception. The THROW mechanism stops execution inside TRY immediately, skips all remaining lines, and transfers control directly to the appropriate CATCH block (specified or generic). This unified behavior ensures that arithmetic errors, out of range indexing, null object calls, and invalid indicator handles never reach the MQL5 runtime; instead, they are converted into structured exception objects and handled predictably. 

This is only true if guards/wrappers are used fully and correctly; otherwise, failures cannot be caught.

An execution layer example, where dangerous operations are validated before reaching the MQL5 runtime. It includes an execution-wrappers class. SafeExecute, the entry point for execution calls, which logs exceptions and records stack context; it integrates directly with the TRY/CATCH macro system. It shows how the components can work together.

//+------------------------------------------------------------------+
//| SafeExecute.mqh                                                  |
//| Try/Catch-like safe execution for MQL5                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\ExceptionResult.mqh>
#include <ExceptionFramework\ExceptionStack.mqh>
#include <ExceptionFramework\GuardMacros.mqh>
#include <ExceptionFramework\Logger.mqh>
#include <ExceptionFramework\TryCatchOOPMacro.mqh>


//+------------------------------------------------------------------+
//|  Safe Function Execute class                                     |
//+------------------------------------------------------------------+
template<typename T, typename F>
class CSafeFunc
  {
public:

   //--- function pointer
   typedef CResult<T> *(*Func)(CExceptionStack &stack);


   //--- Execute safety 
   //--- T validation type \ F return the default type
   static F          SafeExecute(
      Func func,
      CExceptionStack &stack,
      F default_value,
      string context = ""
   )
     {
      CResult<T> *result = NULL;

      TRY
        {
         EX_STACK_PUSH(stack, context);

         result = func(stack);

         if(result == NULL)
            THROW(new CException(
                     "SafeExecute: function returned NULL",
                     EXCEPTION_IS_NULL,
                     __FUNCTION__,
                     CODE_IS_NULL
                  ));

         if(result.IsError())
           {
            CException ex;

            if(result.TryGetError(ex))
               THROW(new CException(ex));
           }

         T value;

         if(!result.TryGetValue(value))
            THROW(new CException(
                     "SafeExecute: failed to get result value",
                     EXCEPTION_VALIDATION,
                     __FUNCTION__,
                     CODE_VALIDATION
                  ));

         EX_STACK_POP(stack);

         delete result;
         result = NULL;

         return value;
        }
      CATCH_ANY(ex)
        {
         Print(
            "SafeExecute caught exception: ",
            ex.ToString()
         );

         EX_STACK_POP(stack);

         if(result != NULL)
           {
            delete result;
            result = NULL;
           }
        }
      FINALLY
        {
        }

      return default_value;
     }
  };

SafeExecute.mqh

  • Usage Concept(this is the basic implementation):
//+------------------------------------------------------------------+
//|  OpenTrade example function returns CResult object               |
//+------------------------------------------------------------------+
CResult<bool>* OpenTrade(CExceptionStack &stack)
  {
   EX_STACK_PUSH(stack, "OpenTrade()");

   double risk = 0.0;
   GUARD_ZERO_DIVIDE(risk, "OpenTrade()");

//--- unreachable, but safe
   EX_STACK_POP(stack);
   return CResult<bool>::Ok(true);
  }

//+------------------------------------------------------------------+
//|  OnTick                                                          |
//+------------------------------------------------------------------+
void OnTick(void)
  {

//--- Manually guarding the exception without Try/Catch expressions
//--- The first simple step
   CSafeFunc<bool,int>::Func func = OpenTrade;

   int value = CSafeFunc<bool,int>::SafeExecute(
                  func,
                  ex_stack,
                  -1,     // default_value
                  "OpenTrade"
               );

//--- if there is an error,shows the default_value,otherwise it is OK
   Print("Value = ", value);

  }

ExampleEA - Safe Guard.mq5

  • Output:
SafeExecute caught exception: [FATAL] [ZeroDivide Exception] Division by zero [Error Code: 2006] (Context: OpenTrade()) Time=2025.12.19 15:10:44
Value = -1

Functional TRY/CATCH — the manual detecting errors (zero-divide or loop triggered exceptions), THROW breaks execution instantly, and THROWN ensures loop propagation after breaking from inside loop. It supports nested TRY blocks.

//+------------------------------------------------------------------+
//|  ExampleEA - TryCatch Func macros.mq5                            |
//|  Include files                                                   |
//+------------------------------------------------------------------+
#include <ExceptionFramework\TryCatchFuncMacro.mqh>

//+------------------------------------------------------------------+
//|  OnInit                                                          |
//+------------------------------------------------------------------+
void OnInit()
  {

   TRY
     {
      double risk = 0.0;

      //--- Manually checking the potential error

      if(risk == 0.0)
        {
         THROW(
            CZeroDivideException("Risk value is zero (division by zero prevented)",
                                 __FUNCTION__,
                                 EX_SEVERITY_FATAL)
         );
        }

      //--- doesnt execute the next lines

      Print("doesnt execute these lines !!!");
      double lot = 1000.0 / risk;
      Print("Lot = ", lot);
     }
   CATCH(ex)
     {
      Print("Handled exception: ", ex.ToString());
     }
   FINALLY
     {
      //--- cleanup / resource release
     }

//--- Manually checking the potential error inside a loop

   TRY
     {
      for(int i=0; i<20; i++)
        {

         if(i==10)
            THROW(
               CException(
                  "i == 10",
                  EXCEPTION_GENERIC,
                  __FUNCTION__,
                  CODE_GENERIC,
                  EX_SEVERITY_FATAL
               )
            );

        }
      //--- **mandatory : if THROW happens in while/for loop,
      //--- this will break the TRY block into CATCH block
      THROWN;

      //--- the next line are not executed
      Print("won't be executed here !!!");
     }
   CATCH(ex)
     {
      Print("Handled exception: ", ex.ToString());
     }
   FINALLY
     {
      //--- cleanup / resource release
     }

//--- Nested TRY
   NestedTryCatch();
  }

//+------------------------------------------------------------------+
//| NestedTryCatch                                                   |
//+------------------------------------------------------------------+
void NestedTryCatch()
  {

//--- 1st TRY
   TRY
     {

      //--- 2nd TRY
      TRY{

         //--- 3rd TRY
         TRY{
            THROW(
               CException(
                  "3rd TRY",
                  EXCEPTION_GENERIC,
                  __FUNCTION__,
                  CODE_GENERIC,
                  EX_SEVERITY_FATAL
               ))}

         CATCH(ex) {}
         FINALLY{}
         //--- 3rd TRY

         THROW(
            CException(
               "2nd TRY",
               EXCEPTION_GENERIC,
               __FUNCTION__,
               CODE_GENERIC,
               EX_SEVERITY_FATAL
            ))
        }

      CATCH(ex) {}
      FINALLY{}
      //--- 2nd TRY

     }
   CATCH(ex)
   FINALLY{}
//--- 1st TRY

  }
//+------------------------------------------------------------------+
//|  OnTick                                                          |
//+------------------------------------------------------------------+
void OnTick(void)
  {}

ExampleEA - TryCatch Func macros.mq5

  • Output:

Handled exception: [FATAL] [ZeroDivide Exception] Risk value is zero (division by zero prevented) [Error Code: 2006] (Context: OnInit) Time=2026.08.12 12:08:47 Handled exception: [FATAL] [Generic Exception] i == 10 [Error Code: 2000] (Context: OnInit) Time=2026.08.12 12:08:47

OOP Style zero-divide handling — the number<double> wrapper and CHECK_ZERO_DIVIDE prevents only before a dangerous operation is performed for division by zero. Also it checks the return value if there is no breaking.

//+------------------------------------------------------------------+
//|  TryCatch AnyEX OOP Sample                                       |
//+------------------------------------------------------------------+
void TryCatch_AnyEX_OOP_Sample()
  {

   number<double> d(10.5);
   number<double> x;
   number<double> y;
   x = 0;

//--- catch zero-divide by predefined macro checks

   TRY
     {
      CHECK_ZERO_DIVIDE(y = d / x;)

      if(!y.HasError())
         Print("y Value :"+(string)y.Value());

      Print("doesnt execute this line !");
     }
   CATCH_ANY(ex)
     {
      Print("ex error: ", ex.ToString());
     }
   FINALLY
   {}


//--- Manually catch and throwing the exception to break the next execution codes

   int db_hnd = INVALID_HANDLE;

   TRY
     {
      db_hnd = DatabaseOpen("Demo.db",DATABASE_OPEN_COMMON);
      if(db_hnd == INVALID_HANDLE)
        {
         THROW(new CDatabaseException("Failed to open database",
                                      __FUNCTION__,
                                      EX_SEVERITY_FATAL));
        }


      //--- the next lines are not executed in TRY block

      Print("doesnt execute the next lines");
      if(DatabaseTableExists(db_hnd, "demo_table"))
         Print("Table exist !");
     }
   CATCH_ANY(ex)
     {
      Print("Database open failed: ", ex.ToString());
     }
   FINALLY
     {
      if(db_hnd != INVALID_HANDLE)
         DatabaseClose(db_hnd);
     }

  }

ExampleEA - TryCatch OOP macros.mq5

  • Output:

ex error: [FATAL] [ZeroDivide Exception] division by zero [Error Code: 2006] (Context: number<double>::operator/) Time=2026.08.08 12:43:38
Database open failed: [FATAL] [Database Exception] Failed to open database [Error Code: 2001] (Context: TryCatch_AnyEX_OOP_Sample) Time=2026.08.08 12:43:38

Specified exception filtering — shows how TRY/CATCH can catch a specific exception class (e.g., CZeroDivideException or CDatabaseException) instead of handling all errors generically. The specified CATCH receives only the matching exception, skips all remaining TRY lines, and other exception types bypass that CATCH entirely, if there is a CATCH_ANY before or after, will be matched too anyway.

//+------------------------------------------------------------------+
//|  TryCatch SpecifiedEX OOP Sample                                 |
//+------------------------------------------------------------------+
void TryCatch_SpecifiedEX_OOP_Sample()
  {

   number<double> d(10.5);
   number<double> x;
   number<double> y;
   x = 0;

//--- Auto catch by Macros and the next control flow will go to specified exception,
//--- otherwise the catch statement won't execute

   TRY
     {
      CHECK_ZERO_DIVIDE(y = d / x;);

      //--- the next lines are not executed in TRY block

      string dumb_str="doesnt execute this line !";
      Print("doesnt execute this line !");
     }
   CATCH(CArrayBoundsException, ex1)
     {
      Print("Array bounds happened: ", ex1.ToString());
     }
   CATCH(CZeroDivideException, ex2)
     {
      Print("Zero divide happened: ", ex2.ToString());
     }
   CATCH(CDatabaseException, ex3)
     {
      Print("Database open failed :", ex3.ToString());
     }
   FINALLY
   {}


//--- Manually catch and throwing the exception to break the next execution codes

   int db_hnd = INVALID_HANDLE;

   TRY
     {
      db_hnd = DatabaseOpen("Demo.db",DATABASE_OPEN_COMMON);
      if(db_hnd == INVALID_HANDLE)
        {
         THROW(new CDatabaseException("Failed to open database",
                                      __FUNCTION__,
                                      EX_SEVERITY_FATAL));
        }

      //--- the next lines are not executed in TRY block

      Print("doesnt execute the next lines");
      if(DatabaseTableExists(db_hnd, "demo_table"))
         Print("Table exist !");
     }
   CATCH(CDatabaseException, ex)
     {
      Print("Database open failed: ", ex.ToString());
     }
   FINALLY
     {
      if(db_hnd != INVALID_HANDLE)
         DatabaseClose(db_hnd);
     }

  }

ExampleEA - TryCatch OOP macros.mq5

  • Output:

Zero divide happened: [FATAL] [ZeroDivide Exception] division by zero [Error Code: 2006] (Context: number<double>::operator/) Time=2026.08.08 12:43:38
Database open failed: [FATAL] [Database Exception] Failed to open database [Error Code: 2001] (Context: TryCatch_SpecifiedEX_OOP_Sample) Time=2026.08.08 12:43:38

Array‑bounds handling — the array<T> wrapper and CHECK_ARRAY_BOUNDS prevents only before a dangerous operation is performed for invalid read/write indices and throws  CArrayBoundsException, and is handled in CATCH (specified or generic) in a fully controlled way.

//+------------------------------------------------------------------+
//|  TryCatch AnyEX Array OOP Sample                                 |
//+------------------------------------------------------------------+
void TryCatch_AnyEX_Array_OOP_Sample()
  {

   array<double> arr;

//--- Checking an array bounds and it will throw an exception as specified exception object at catch block
//--- Trying to set the array a value

   TRY
     {
      CHECK_ARRAY_BOUNDS(arr[SET(0)]=1.2;);

      string dumb_str="doesnt execute this line !";
      Print("doesnt execute this line !");
     }
   CATCH(CArrayBoundsException, ex)
     {
      Print("Array out of range happened: ", ex.ToString());
     }
   FINALLY
   {}

//--- Trying to get the array a value

   TRY
     {

      number<double> value;

      CHECK_ARRAY_BOUNDS(value = arr[GET(10)]);


      if(!value.HasError())
         Print("value = " + (string)value.Value());

      string dumb_str="doesnt execute this line !";
      Print("doesnt execute this line !");
     }
   CATCH_ANY(ex)
     {
      Print("Exception happened: ", ex.ToString());
     }
   FINALLY
   {}

  }

ExampleEA - TryCatch OOP macros.mq5

  • Output:

Array out of range happened: [FATAL] [ArrayOutOfRange Exception] index out of bounds [Error Code: 2008] (Context: array<double>::operator[]) Time=2026.08.08 12:43:38
Exception happened: [FATAL] [ArrayOutOfRange Exception] index out of bounds [Error Code: 2008] (Context: array<double>::operator[]) Time=2026.08.08 12:43:38

Null pointer & Invalid handle protections — CHECK_NOT_NULL and CHECK_VALID_HANDLE prevents before a dangerous operation is performed for null‑pointer access and invalid indicator handles, the guard throws a specified exception, TRY stops immediately, remaining lines are skipped, and CATCH prints the error while the EA continues running normally.

//+------------------------------------------------------------------+
//|  ExampleEA - TryCatch OOP macros.mq5                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  a dumb class for testing                                        |
//+------------------------------------------------------------------+
class dumb
  {
public:
                     dumb(void);
                    ~dumb(void);
   //--- DoSomething                                                     
   void              DoSomething() {Print("doesnt execute !!!");};
  };

//+------------------------------------------------------------------+
//|  TryCatch NullEX OOP Sample                                      |
//+------------------------------------------------------------------+
void TryCatch_NullEX_OOP_Sample()
  {

   dumb* dumb_empty_obj=NULL;

//--- Try to call a function of null object,but after occurring exception,
//--- the next codes are nor executed,then will go to catch any exception expression

   TRY
     {
      CHECK_NOT_NULL(dumb_empty_obj);

      dumb_empty_obj.DoSomething();
      string dumb_str="doesnt execute this line !";
      Print("doesnt execute this line !");
     }
   CATCH_ANY(ex)
     {
      Print("Null exception happened: ", ex.ToString());
     }
   FINALLY
   {}


//--- Try to get an indicator buffer of invalid handle,After checking the handle,
//--- the desired exception is occurred and the next codes are not executed.

   int invalid_h=-1;
   double empty_array[];

   TRY
     {
      CHECK_VALID_HANDLE(invalid_h);

      CopyBuffer(invalid_h,0,0,10,empty_array);

      string dumb_str="doesnt execute this line !";
      Print("doesnt execute this line !");
     }
   CATCH_ANY(ex)
     {
      Print("Exception happened: ", ex.ToString());
     }
   FINALLY
   {}

  } 

ExampleEA - TryCatch OOP macros.mq5

  • Output:
Null exception happened: [FATAL] [IsNull Exception] null exception [Error Code: 2007] (Context: TryCatch_NullEX_OOP_Sample) Time=2026.08.08 12:43:38
Exception happened: [FATAL] [InvalidHandle Exception] invalid handle exception [Error Code: 2009] (Context: TryCatch_NullEX_OOP_Sample) Time=2026.08.08 12:43:38

Also supports nested TRY/CATCH blocks.

//+------------------------------------------------------------------+
//|  Nested TryCatch                                                 |
//+------------------------------------------------------------------+
void TryCatch_Nested_OOP_Sample()
  {

   number<double> d(10.5);
   number<double> x;
   number<double> y;
   x = 0;

   array<double> arr;

//--- 1st TRY
   TRY
     {

      //--- 2nd nested TRY
      TRY
        {

         //--- 3rd nested TRY
         TRY{CHECK_ZERO_DIVIDE(y = d / x)}
         CATCH(CZeroDivideException, ex) {Print("3rd TRY: ", ex.ToString());}
         FINALLY
         //--- end  3rd nested TRY


         CHECK_ZERO_DIVIDE(y = d / x)
        }
      CATCH_ANY(ex)
        {
         Print("2nd TRY: ", ex.ToString());
        }
      FINALLY{}
      //---end 2nd nested TRY


      //--- 4th nested inside 1st TRY
      TRY{CHECK_ZERO_DIVIDE(y = d / x)}
      CATCH_ANY(ex) {Print("4th TRY: ", ex.ToString());}
      FINALLY{}
      //--- end 4th nested inside 1st TRY



      CHECK_ARRAY_BOUNDS(arr[SET(0)]=1.2;);
     }
   CATCH(CArrayBoundsException, ex)
     {
      Print("1st TRY: ", ex.ToString());
     }
   FINALLY
     {
     }
//---end 1st TRY

  }

ExampleEA - TryCatch OOP macros.mq5

  • Output:

3rd TRY: [FATAL] [ZeroDivide Exception] division by zero [Error Code: 2006] (Context: number<double>::operator/) Time=2026.08.08 12:43:38
2nd TRY: [FATAL] [ZeroDivide Exception] division by zero [Error Code: 2006] (Context: number<double>::operator/) Time=2026.08.08 12:43:38
4th TRY: [FATAL] [ZeroDivide Exception] division by zero [Error Code: 2006] (Context: number<double>::operator/) Time=2026.08.08 12:43:38
1st TRY: [FATAL] [ArrayOutOfRange Exception] index out of bounds [Error Code: 2008] (Context: array<double>::operator[]) Time=2026.08.08 12:43:38


Conclusion

This article presented a practical, MQL5-native error-handling framework that prioritizes prevention, structured error propagation, and diagnostic traceability over attempting to intercept runtime faults within the MetaTrader virtual machine. The framework combines a unified error representation (CException), a result type (CResult<T>), manual stack tracing (CExceptionStack), persistent logging (CLogger), and a set of guards, wrappers, and TRY/CATCH-style macros supporting both functional and object-oriented approaches.

Together, these components provide a consistent way to report errors, preserve diagnostic context, and trace failures through complex Expert Advisor workflows. By validating preconditions before potentially unsafe operations, the framework can reduce the risk of preventable runtime failures while improving code readability, debugging, and maintainability.

The framework supports incremental adoption. Developers can begin by replacing ad hoc error checks with CResult<T> and GUARD_* macros in error-prone modules, then introduce CExceptionStack and CLogger for more detailed diagnostics. TRY/CATCH-style macros and numeric or array-access wrappers can subsequently be integrated where structured, cooperative error handling is required.

An important limitation remains: the framework cannot intercept fatal VM-level runtime faults, and its preventive safeguards apply only to operations covered by the corresponding guards and wrappers. Code that bypasses these mechanisms remains unprotected. Reliable use in live trading therefore requires consistent integration, appropriate validation, and thorough testing.

The complete framework source code is included in the attached archive, with files organized for integration into MQL5 projects. This provides a reusable foundation for building more predictable, diagnosable, and maintainable Expert Advisors without external dependencies.

#  Name Type Description
1 ExampleEA - Safe Guard.mq5 Expert Advisor
Guard examples
2 ExampleEA - TryCatch Func macros.mq5 Expert Advisor
Try/Catch functionality examples
3 ExampleEA - TryCatch OOP macros.mq5 Expert Advisor
Try/Catch OOP examples
4 ExampleFunctions.mqh Include file Guard function sample
5 Exception.mqh Include file
The main exception class
6 ExceptionResult.mqh Include file
The exception result controller class
7 ExceptionStack.mqh Include file
The exception stack controller class
8 GuardMacros.mqh Include file
Guard macro to control the produced error flow
9 Logger.mqh Include file
The class to record the exception trace
10 number.mqh Include file The number class wrapper
11 array.mqh Include file
The array class wrapper
12 SafeExecute.mqh Include file
The execution class sample
13 TryCatchBlock.mqh Include file
The class in OOP Try/Catch style
14 TryCatchFuncMacro.mqh Include file
Try/Catch functionality macro
15 TryCatchOOPMacro.mqh Include file
Try/Catch OOP macro
Attached files |
MQL5.zip (20.79 KB)
Nonlinear OHLC Features Derived from Elliptic Curves Nonlinear OHLC Features Derived from Elliptic Curves
The article discusses the projection of EURUSD daily candlesticks onto the secp256k1 elliptic curve and the extraction of 96 features (EC+TA) to predict the direction of the next candlestick in CatBoost. The price mapping to the curve and the training pipeline on 2,000 D1 bars are shown; the full model achieves a test AUC of 0.6508, with EC features contributing 60.6%. The materials are suitable for reproduction in Python/MetaTrader 5.
Agent Based Market Simulation: Growing a Market From Rules, and Using It to Catch an Overfitted Strategy Agent Based Market Simulation: Growing a Market From Rules, and Using It to Catch an Overfitted Strategy
A compact MetaTrader 5 library builds a synthetic market from agents: a minority game, a zero‑intelligence double auction, a stylized‑facts analyzer, and a parameter scanner. The result is a testbed with known answers, providing the control real price backtests lack. Each result is cross‑checked by the model's phase transition, a matching Python implementation, and detection of a planted edge.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Adaptive Trade Management in MQL5: Building a Broker-Aware, Deterministic, and Explainable Exit Engine Adaptive Trade Management in MQL5: Building a Broker-Aware, Deterministic, and Explainable Exit Engine
This article presents an MQL5 trade-management engine that converts exit rules into proposal generators. A policy engine ranks all proposals by a configurable reason priority with documented tie-breakers. A broker-aware validator checks the candidates in that order and falls back to the next one on rejection, and an execution adapter sends the first accepted request. Reconciliation confirms the result on the position before the internal state changes. The engine uses R-multiples for thresholds, persists intratrade state, and records a decision trace for each managed position on every tick.