MQL5 Programming Language Guide — Algorithmic Trading for MetaTrader 5
MQL5 (MetaQuotes Language 5) is a C++-like programming language built for developing algorithmic trading robots, custom indicators, and analytical tools on the MetaTrader 5 platform. In this tutorial, you’ll learn the complete MQL5 ecosystem — from program types and event handlers to trading functions and backtesting — so you can build, test, and deploy your own automated trading strategies.
Why it matters: Algorithmic trading now accounts for over 70% of forex and CFD volume. Writing your own trading robots gives you full control over entry logic, risk management, and execution speed — no black boxes, no middlemen. A well-tested EA (Expert Advisor) can execute trades 24/7 with zero emotional bias.
Real-world use: Hedge funds and retail traders alike use MQL5 EAs to automate everything from simple moving-average crossovers to multi-asset machine-learning strategies that stream ONNX model inferences directly inside MetaTrader 5.
What You’ll Learn
- The MQL5 language syntax and MetaEditor IDE
- Program types: EAs, Custom Indicators, Scripts, Services, Libraries, Include Files
- Event-driven programming: OnInit, OnDeinit, OnTick, OnTimer, OnChartEvent
- Trading functions: PositionOpen, OrderSend, OrderClose, and trade request/result pattern
- Built-in technical indicators: iMA, iRSI, iBollingerBands, iMACD
- Backtesting and optimization in the Strategy Tester
- File I/O, custom graphics, and ONNX-based Python integration
- Key differences from MQL4
Learning Path
flowchart LR
A[MQL5 Basics<br/>You are here] --> B[Program Types & Event Handlers]
B --> C[Trading Functions & Indicators]
C --> D[Backtesting & Optimization]
D --> E[Build a Moving Average Crossover EA]
What Is MQL5?
MQL5 is the native programming language of the MetaTrader 5 trading platform. Its syntax is heavily inspired by C++: you get classes, inheritance, polymorphism, operator overloading, templates, namespaces, and a rich standard library for trading, charting, and file operations.
Unlike MQL4, MQL5 is position-oriented (not order-oriented), supports multi-currency backtesting, and runs 32-bit floating-point calculations for greater precision in technical indicators. It compiles to .ex5 executable files that the MT5 terminal loads at runtime.
MetaEditor IDE
MetaTrader 5 ships with MetaEditor — a full-featured IDE with syntax highlighting, code completion (IntelliSense), a built-in debugger, a profiler, and direct integration with the Strategy Tester. You write code in MetaEditor, compile it with F7, and attach the resulting .ex5 to a chart — all without leaving the MT5 terminal.
Key MetaEditor features:
- MQL5 Wizard — generates scaffold code for EAs, indicators, and scripts
- Debugger — step through tick-by-tick execution with variable watches
- Profiler — identify slow functions that delay trade execution
- Code Browser — navigate includes, resources, and project files
Program Types
MQL5 defines six program types. Each has a distinct purpose and entry point:
flowchart TD
MT5[MetaTrader 5 Terminal] --> EA[Expert Advisor<br/>OnTick, OnTrade, OnTimer]
MT5 --> CI[Custom Indicator<br/>OnCalculate]
MT5 --> SC[Script<br/>OnStart — runs once]
MT5 --> SV[Service<br/>OnStart — runs in background]
MT5 --> INC[Include Files<br/>.mqh — shared code]
MT5 --> LIB[Libraries<br/>.ex5 — compiled, exported functions]
Expert Advisors (EAs)
EAs are automated trading systems that run continuously on a chart. They respond to every price tick via OnTick(), manage positions, and can schedule tasks via OnTimer(). An EA is the MQL5 equivalent of a trading bot.
//+------------------------------------------------------------------+
//| Simple Moving Average Crossover Expert Advisor |
//+------------------------------------------------------------------+
input int FastMAPeriod = 10;
input int SlowMAPeriod = 30;
input double LotSize = 0.1;
int fastHandle, slowHandle;
int OnInit() {
fastHandle = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
return INIT_SUCCEEDED;
}
void OnTick() {
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
if(CopyBuffer(fastHandle, 0, 0, 3, fast) < 3) return;
if(CopyBuffer(slowHandle, 0, 0, 3, slow) < 3) return;
// No existing position — check for entry
if(PositionSelect(_Symbol) == false) {
if(fast[1] > slow[1] && fast[2] <= slow[2]) {
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = LotSize;
req.type = ORDER_TYPE_BUY;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.deviation= 10;
OrderSend(req, res);
}
}
}
void OnDeinit(const int reason) {
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
}Expected behavior: When the fast SMA crosses above the slow SMA, the EA opens a buy position of 0.1 lots. It only enters once — PositionSelect prevents re-entry while a position is open. On shutdown, both indicator handles are released.
Custom Indicators
Indicators process price data and display custom buffers on the chart. The core function is OnCalculate():
//+------------------------------------------------------------------+
//| Custom Indicator — Price Change % |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
double changeBuffer[];
int OnInit() {
SetIndexBuffer(0, changeBuffer, INDICATOR_DATA);
IndicatorSetString(INDICATOR_SHORTNAME, "PriceChange%");
return INIT_SUCCEEDED;
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[]) {
for(int i = (prev_calculated > 0 ? prev_calculated - 1 : 1); i < rates_total; i++) {
changeBuffer[i] = (close[i] - close[i-1]) / close[i-1] * 100.0;
}
return rates_total;
}Expected behavior: This indicator plots the percentage price change from bar to bar in a separate window below the chart. On the first calculation it iterates from bar 1 onward; on subsequent ticks it only processes new bars.
Scripts
Scripts execute once and immediately exit. They are useful for one-off tasks like closing all positions or exporting trade history:
//+------------------------------------------------------------------+
//| Close All Positions Script |
//+------------------------------------------------------------------+
void OnStart() {
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(PositionSelectByTicket(PositionGetTicket(i))) {
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = PositionGetString(POSITION_SYMBOL);
req.volume = PositionGetDouble(POSITION_VOLUME);
req.deviation = 10;
req.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.price = (req.type == ORDER_TYPE_SELL)
? SymbolInfoDouble(req.symbol, SYMBOL_BID)
: SymbolInfoDouble(req.symbol, SYMBOL_ASK);
OrderSend(req, res);
}
}
}Expected behavior: The script iterates through all open positions (in reverse to avoid index shifting), closes each one at the current market price, and exits.
Services, Libraries, and Include Files
- Services run in the background without a chart — ideal for monitoring news feeds, sending email alerts, or logging trade data.
- Libraries are compiled
.ex5files that export reusable functions (marked with#property libraryandexportkeywords). - Include Files (
.mqh) are text-based header files shared across projects via#include.
Event Handlers
MQL5 is event-driven. The runtime calls these handlers automatically:
| Handler | Trigger | Used In |
|---|---|---|
OnInit() | EA/indicator loaded on chart | Setup: indicator handles, file opens, variable init |
OnDeinit(const int reason) | EA/indicator removed | Cleanup: release handles, close files |
OnTick() | New price tick arrives | Core trading logic for EAs |
OnTimer() | Timer interval expires | Periodic tasks (email reports, resets) |
OnChartEvent(...) | Chart events: clicks, key presses, objects | Interactive panels & custom GUI |
OnTrade() | Trade transaction occurs | Post-trade logging, position monitoring |
OnCalculate(...) | New bar or tick for indicators | Indicator calculation |
Trading Functions
MQL5 uses a request/result pattern for all trading operations. You populate a MqlTradeRequest struct, call OrderSend(), and inspect the MqlTradeResult:
| Function | Purpose |
|---|---|
OrderSend(MqlTradeRequest &req, MqlTradeResult &res) | Send trade request (open, modify, close, delete) |
PositionSelect(const string symbol) | Select a position by symbol |
PositionSelectByTicket(ulong ticket) | Select a position by ticket number |
PositionGetDouble(ENUM_POSITION_INFO_DOUBLE property) | Get position property (volume, price, etc.) |
PositionGetInteger(ENUM_POSITION_INFO_INTEGER property) | Get integer position property (type, magic, etc.) |
HistorySelect(datetime from, datetime to) | Select closed positions and deals |
HistoryDealGetDouble(ulong ticket, ENUM_DEAL_INFO_DOUBLE prop) | Get deal property |
OrderCalcMargin(...) | Calculate margin required for an order |
The MqlTradeRequest struct fields include:
struct MqlTradeRequest {
ENUM_TRADE_REQUEST_ACTIONS action; // TRADE_ACTION_DEAL, TRADE_ACTION_PENDING, etc.
ulong magic; // EA identifier
ulong order; // Order ticket (for modifications)
string symbol; // Trading instrument
double volume; // Lot size
double price; // Open price
double sl; // Stop Loss
double tp; // Take Profit
ENUM_ORDER_TYPE type; // ORDER_TYPE_BUY, ORDER_TYPE_SELL, etc.
...
};Built-in Technical Indicators
MQL5 wraps MetaTrader’s indicator calculations with simple handle-based functions:
| Function | Indicator | Return |
|---|---|---|
iMA(symbol, period, ma_period, shift, method, applied) | Moving Average | Handle |
iRSI(symbol, period, rsi_period, applied) | Relative Strength Index | Handle |
iBollingerBands(symbol, period, bands_period, shift, dev, applied) | Bollinger Bands | Handle |
iMACD(symbol, period, fast, slow, signal, applied) | MACD | Handle |
iATR(symbol, period, atr_period) | Average True Range | Handle |
iStochastic(symbol, period, K, D, slowing, method, price) | Stochastic Oscillator | Handle |
iADX(symbol, period, adx_period) | Average Directional Index | Handle |
After creating a handle with i*, call CopyBuffer(handle, buffer_index, start_pos, count, dest_array) to pull data into your code. Always check that CopyBuffer returns the expected number of values.
Backtesting and Optimization
The Strategy Tester in MT5 tests EAs on historical data. Its key features:
- Every tick mode — models every bid/ask tick for maximum accuracy
- Open prices only — fast tests using OHLC data
- Multi-currency — test EAs that trade more than one symbol simultaneously
- Forwarding — walk-forward optimization across date ranges
- Genetic optimization — finds optimal parameter combinations without brute-force
flowchart LR
A[Define Parameters<br/>input int, double, bool] --> B[Run Backtest<br/>Date Range + Mode]
B --> C[Analyze Results<br/>Profit, Drawdown, Sharpe, Trades]
C --> D[Optimize Parameters<br/>Genetic or Brute-Force]
D --> E{Good Enough?}
E -->|No| B
E -->|Yes| F[Forward Test<br/>on Live Demo]
File Operations and Custom Graphics
MQL5 supports FileOpen, FileWrite, FileRead, and FileClose for reading/writing CSV, binary, and text files. Data is stored in the /Files/ subdirectory of the MT5 data folder.
Custom graphics use ObjectCreate() and ObjectSet*() functions to draw shapes, labels, buttons, and charts directly on the terminal:
ObjectCreate(0, "btnBuy", OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, "btnBuy", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "btnBuy", OBJPROP_YDISTANCE, 10);
ObjectSetString(0, "btnBuy", OBJPROP_TEXT, "BUY");Python Integration via ONNX Models
MQL5 can load and run ONNX (Open Neural Network Exchange) models directly using the OnnxRuntime library. This lets you train a machine-learning model in Python (with PyTorch, TensorFlow, or Scikit-Learn), export it to .onnx format, and run inference inside an EA:
#include <OnnxRuntime.mqh>
long modelHandle = OnnxCreateFromBuffer(modelData, ONNX_DEFAULT);
void OnTick() {
float inputData[1][10]; // 10 features
float outputData[1][1]; // single prediction
// Populate inputData from current market conditions...
OnnxRun(modelHandle, ONNX_NO_CONVERSION, inputData, outputData,
ONNX_NO_CONVERSION);
if(outputData[0][0] > 0.7) {
// High-confidence buy signal
}
}This bridges the gap between quantitative research in Python and live execution in MT5 — no middle layer required.
MQL5 vs MQL4 — Key Differences
| Feature | MQL5 | MQL4 |
|---|---|---|
| Paradigm | Object-oriented | Procedural / limited OOP |
| Trade model | Position-oriented | Order-oriented |
| Trade functions | PositionOpen, PositionClose | OrderSend, OrderClose |
| Event handlers | OnInit, OnTick, OnDeinit | init, start, deinit |
| Indicator handles | Handle-based (iMA returns handle) | Call-based (iMA returns value directly) |
| Multi-currency tester | Yes | No |
| Timeframes | 21 timeframes | 9 timeframes |
| ONNX / Python bridge | Built-in | Not supported |
| Compiler | 32-bit floats, 64-bit integers | 64-bit doubles throughout |
Common Errors
1. Array out of range when reading indicator buffers
Always call ArraySetAsSeries(buffer, true) to reverse the array so index 0 is the current (newest) bar. Then check CopyBuffer return value before accessing elements.
2. OrderSend returns false with error 4756
This means the EA does not have automated trading enabled. Check Tools → Options → Expert Advisors → “Allow Automated Trading” and make sure the EA’s “Allow live trading” checkbox is checked on the chart.
3. Indicator handle returns INVALID_HANDLE (-1)
The indicator parameters are wrong or the symbol/timeframe is invalid. Validate every handle after creation and return INIT_FAILED if any is invalid.
4. PositionSelect fails for newly opened positions
PositionSelect queries the terminal cache, which may not update instantly. Use OnTrade() or a short Sleep() before checking after an OrderSend.
5. Backtest results differ from live trading
This is usually caused by tick modelling — use “Every tick” mode for realistic tests. Also check that OrderSend deviation, slippage, and spread modelling match real broker conditions.
6. CopyBuffer returns -1 (handle not ready)
The indicator calculates asynchronously. Wait one tick or use BarsCalculated(handle) to confirm the handle has data.
Practice Questions
What event handler contains the core trading logic of an EA?
OnTick()— it fires on every new price tick and is where you check conditions and send orders.How do you read an indicator value in MQL5? Create a handle with
iMA()(oriRSI(), etc.), then callCopyBuffer(handle, 0, 0, count, array)to copy values into a dynamic array.What is the difference between
OrderSendin MQL5 vs MQL4? MQL5 uses a request/result pattern withMqlTradeRequest/MqlTradeResultstructs; MQL4 uses direct parameters. MQL5 distinguishes positions from orders; MQL4 treats everything as orders.Why does my EA compile but not trade on the chart? Most likely: automated trading is disabled in the terminal, or the EA’s “Allow live trading” flag is unchecked, or
OnInit()returnedINIT_FAILEDbecause an indicator handle was invalid.How can I integrate a machine-learning model with MQL5? Export your model from Python to ONNX format, then use
OnnxCreateFromBuffer()andOnnxRun()inside your EA for live inference.
Challenge: Write an EA that opens a buy position when the RSI(14) crosses below 30 (oversold) and closes it when RSI crosses above 70 (overbought). Add a trailing stop-loss that follows price by 50 points.
Mini Project — Build a Simple Moving Average Crossover EA
Build a production-ready EA that trades the classic SMA crossover strategy:
Specification:
- Two SMAs: fast (10) and slow (40)
- Buy when fast crosses above slow
- Sell when fast crosses below slow
- Fixed stop-loss of 200 points
- Take-profit of 400 points
- Maximum 1 position at a time
- Print trade details to the Experts log
// SMA Crossover EA — Core Logic
input int FastMA = 10;
input int SlowMA = 40;
input double Lot = 0.1;
input int StopLoss = 200;
input int TakeProfit = 400;
int fastHandle, slowHandle;
int OnInit() {
fastHandle = iMA(_Symbol, _Period, FastMA, 0, MODE_SMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowMA, 0, MODE_SMA, PRICE_CLOSE);
return (fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
? INIT_FAILED : INIT_SUCCEEDED;
}
void OnDeinit(const int reason) {
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
}
void OnTick() {
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
if(CopyBuffer(fastHandle, 0, 0, 3, fast) < 3) return;
if(CopyBuffer(slowHandle, 0, 0, 3, slow) < 3) return;
bool crossoverBuy = fast[1] > slow[1] && fast[2] <= slow[2];
bool crossoverSell = fast[1] < slow[1] && fast[2] >= slow[2];
if(PositionSelect(_Symbol)) {
long posType = PositionGetInteger(POSITION_TYPE);
if((posType == POSITION_TYPE_BUY && crossoverSell) ||
(posType == POSITION_TYPE_SELL && crossoverBuy)) {
ClosePosition();
}
return;
}
if(crossoverBuy) OpenOrder(ORDER_TYPE_BUY);
if(crossoverSell) OpenOrder(ORDER_TYPE_SELL);
}
void OpenOrder(ENUM_ORDER_TYPE type) {
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = Lot;
req.type = type;
req.price = (type == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
req.sl = (type == ORDER_TYPE_BUY)
? req.price - StopLoss * _Point
: req.price + StopLoss * _Point;
req.tp = (type == ORDER_TYPE_BUY)
? req.price + TakeProfit * _Point
: req.price - TakeProfit * _Point;
req.deviation = 10;
if(OrderSend(req, res)) {
Print("Opened ", (type == ORDER_TYPE_BUY ? "BUY" : "SELL"),
" | Ticket: ", res.order, " | Price: ", req.price);
} else {
Print("OrderSend failed: ", res.retcode);
}
}
void ClosePosition() {
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = PositionGetString(POSITION_SYMBOL);
req.volume = PositionGetDouble(POSITION_VOLUME);
req.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.price = (req.type == ORDER_TYPE_SELL)
? SymbolInfoDouble(req.symbol, SYMBOL_BID)
: SymbolInfoDouble(req.symbol, SYMBOL_ASK);
req.deviation = 10;
if(OrderSend(req, res)) {
Print("Closed position: ", res.order);
}
}Expected backtest results (default EURUSD, H1, 2024): A simple SMA crossover without filters typically achieves 40–55% win rate with positive expectancy when trend is present. You can improve it by adding a trend filter (e.g., only buy when price > 200 SMA) or a volatility filter (e.g., skip entries when ATR is too low).
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro