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.
| Feature | MQL5 Struct | MQL5 Class |
|---|---|---|
| Default Access | public | private |
| Inheritance | From another struct | From another class (single inheritance) |
| Virtual Methods | Not supported | Supported (polymorphism) |
| Memory Model | Declared objects; no new allocation | Automatic objects or dynamic objects accessed through descriptors |
| Typical Use Case | Data 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 Return | Meaning | Recommended Action |
|---|---|---|
| POINTER_INVALID | Pointer does not reference valid memory | Do not dereference; reinitialize or abort |
| POINTER_DYNAMIC | Allocated with new on the heap | Valid while alive; delete at the end of the owning lifetime |
| POINTER_AUTOMATIC | Automatically 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.