Complete MQL5 guide & code

MQL5 Classes, Structs & Inheritance: OOP Architecture Guide

Master MQL5 object-oriented programming with classes, structs, and inheritance. Learn memory management, access specifiers, virtual methods, and modular EA design.

What this MQL5 guide covers

MQL5 supports object-oriented programming based on C++, including class encapsulation, inheritance, and polymorphism.

Structs in MQL5 default to public member access and serve as lightweight value types for signals, ticks, and configuration parameters.

Classes default to private access, support single inheritance (class Derived : public Base), virtual methods, and heap allocation via new and delete.

MQL5 structs can inherit from structs, and classes from classes. Structs cannot declare virtual functions or be allocated with new.

Struct vs Class in MQL5: Core Differences

Both structs and classes in MQL5 can contain data members, constructor and destructor methods, and member functions. The key structural differences govern default access rights, inheritance capabilities, and allocation patterns.

A struct defaults to public access for its members and methods. A class defaults to private access, enforcing data encapsulation unless public or protected specifiers are declared.

FeatureMQL5 StructMQL5 Class
Default Accesspublicprivate
InheritanceFrom another structFrom another class (single inheritance)
Virtual MethodsNot supportedSupported (polymorphism)
Memory ModelDeclared objects; no new allocationAutomatic objects or dynamic objects accessed through descriptors
Typical Use CaseData records (MqlTick, signals)Trading engines, indicator wrappers

Inheritance and Virtual Methods in MQL5

MQL5 implements single class inheritance using the colon syntax: class CDerived : public CBase. Multiple inheritance is not supported in MQL5.

Declaring methods with the virtual keyword enables runtime polymorphism. A base pointer can store derived class instances and invoke the correct overridden implementation dynamically.

  • virtual void OnTick() = 0; defines a pure virtual method, making the class abstract.
  • Derived classes override methods without altering the calling code interface.
  • Constructors execute from base to derived; destructors execute in reverse order.

Dynamic Allocation and Pointer Safety: CheckPointer

Class objects may be declared directly or allocated with new. MQL5 pointer syntax represents an object descriptor, not a C++ memory address. Lifetime depends on declaration scope and ownership, not a simple struct-versus-class stack/heap rule.

Delete dynamically created objects when their owner no longer needs them; for a global EA-owned object this commonly happens in OnDeinit(). To prevent null pointer exceptions, always verify pointers with CheckPointer(ptr) != POINTER_INVALID before calling methods.

CheckPointer ReturnMeaningRecommended Action
POINTER_INVALIDPointer does not reference valid memoryDo not dereference; reinitialize or abort
POINTER_DYNAMICAllocated with new on the heapValid while alive; delete at the end of the owning lifetime
POINTER_AUTOMATICAutomatically created object (local, global or static scope)Valid to call methods; cleans up automatically

OOP Patterns in the MT5 Standard Library

The MetaTrader 5 Standard Library relies heavily on OOP architecture. Classes such as CTrade, CPositionInfo, COrderInfo, and CAccountInfo encapsulate low-level trade structures into clean methods.

Extending or composing standard library classes inside your custom strategy classes produces modular Expert Advisors that are easy to test, maintain, and adapt to different market regimes.

Implementation details & mechanics

Core implementation pattern

OnInit allocates an RSI strategy through the base-class descriptor and calls its Init method. On each new chart bar, Update copies two completed RSI values and CheckSignal returns a TradeSignal record. The EA logs the signal only; the example does not send an order or implement risk management.

Error handling & safety rules

OnDeinit deletes the dynamically owned strategy, whose destructor releases its indicator handle. There is no CheckExit method or position-close logic in this sample. If initialization fails or buffer copying is incomplete, do not use a stale signal; ownership and data validity remain separate responsibilities.

MQL5 Code Implementation

//+------------------------------------------------------------------+
//|                                  ClassesStructsInheritanceEA.mq5 |
//|                             Copyright 2026, Pineify Guides Series |
//|                                              https://pineify.app |
//+------------------------------------------------------------------+
#property copyright "Pineify Guides Series"
#property link      "https://pineify.app/mql5/classes-structs-inheritance-guide"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- 1. Plain Data Structure (Struct)
struct TradeSignal
  {
   bool     isValid;
   int      direction; // 1 = Buy, -1 = Sell, 0 = Flat
   double   price;
   datetime timestamp;
   string   comment;
  };

//--- 2. Abstract Base Class
class CStrategyBase
  {
protected:
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;

public:
                     CStrategyBase(const string symbol, const ENUM_TIMEFRAMES tf)
                       : m_symbol(symbol), m_timeframe(tf) {}
   virtual          ~CStrategyBase() {}

   // Pure virtual methods enforcing contract in derived classes
   virtual bool      Init() = 0;
   virtual void      Update() = 0;
   virtual TradeSignal CheckSignal() = 0;
  };

//--- 3. Derived Concrete Strategy Class
class CRsiStrategy : public CStrategyBase
  {
private:
   int               m_period;
   double            m_oversold;
   int               m_handle;
   double            m_buffer[];

public:
                     CRsiStrategy(const string symbol, const ENUM_TIMEFRAMES tf,
                                  const int period, const double oversold)
                       : CStrategyBase(symbol, tf),
                         m_period(period),
                         m_oversold(oversold),
                         m_handle(INVALID_HANDLE) {}

   virtual          ~CRsiStrategy()
     {
      if(m_handle != INVALID_HANDLE)
         IndicatorRelease(m_handle);
     }

   virtual bool Init() override
     {
      m_handle = iRSI(m_symbol, m_timeframe, m_period, PRICE_CLOSE);
      if(m_handle == INVALID_HANDLE)
         return false;

      ArraySetAsSeries(m_buffer, true);
      return true;
     }

   virtual void Update() override
     {
      if(m_handle != INVALID_HANDLE)
         if(CopyBuffer(m_handle, 0, 1, 2, m_buffer)!=2) ArrayFree(m_buffer);
     }

   virtual TradeSignal CheckSignal() override
     {
      TradeSignal signal;
      ZeroMemory(signal);

      if(ArraySize(m_buffer) < 2)
         return signal;

      // Detect oversold rebound: previous bar below oversold, latest crossed above
      if(m_buffer[1] < m_oversold && m_buffer[0] >= m_oversold)
        {
         signal.isValid   = true;
         signal.direction = 1; // Long
         signal.price     = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
         signal.timestamp = TimeCurrent();
         signal.comment   = "RSI Oversold Rebound";
        }

      return signal;
     }
  };

//--- Global Variables
CStrategyBase *g_strategy = NULL;
CTrade         g_trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Instantiate derived class through base pointer
   g_strategy = new CRsiStrategy(_Symbol, _Period, 14, 30.0);

   if(CheckPointer(g_strategy) == POINTER_INVALID)
     {
      Print("Failed to allocate strategy instance.");
      return(INIT_FAILED);
     }

   if(!g_strategy.Init())
     {
      Print("Strategy initialization failed.");
      return(INIT_FAILED);
     }

   Print("OOP EA initialized successfully.");
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Safe dynamic memory cleanup
   if(CheckPointer(g_strategy) == POINTER_DYNAMIC)
     {
      delete g_strategy;
      g_strategy = NULL;
     }

   PrintFormat("Strategy instance deleted. Deinit reason: %d", reason);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(CheckPointer(g_strategy) == POINTER_INVALID)
      return;

   static datetime lastBar=0;
   datetime bar=iTime(_Symbol,_Period,0);
   if(bar==0 || bar==lastBar) return;
   lastBar=bar;
   g_strategy.Update();
   TradeSignal sig = g_strategy.CheckSignal();

   if(sig.isValid && sig.direction == 1)
     {
      PrintFormat("Signal triggered: %s at price %.5f", sig.comment, sig.price);
      // Execute order via CTrade
     }
  }

Copy this code into MetaEditor, save it in your MQL5 folder (Experts, Scripts, or Include), and compile with F7.

Generate custom MQL5 code with Pineify AI

Describe the trade execution, indicator calculations, risk management, or data processing logic you need. Pineify generates editable MQL5 source code. Review its logic, request a syntax check, and compile and test the final file in MetaEditor.

Frequently Asked Questions

Related MQL5 pages

Technical references

Risk and testing note

These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.