Try/Catch style in MQL5: Bringing Modern Error Management to Algorithmic Trading
Table of contents:
- Introduction
- Core Exception Model Design
- Defensive Guards and TRY/CATCH Macros
- Practical Examples
- Conclusion
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.
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.
//+------------------------------------------------------------------+ //| 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.
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.
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 |
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Nonlinear OHLC Features Derived from Elliptic Curves
Agent Based Market Simulation: Growing a Market From Rules, and Using It to Catch an Overfitted Strategy
Features of Experts Advisors
Adaptive Trade Management in MQL5: Building a Broker-Aware, Deterministic, and Explainable Exit Engine
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use