Skip to content
MQL5 Programming Language Guide — Algorithmic Trading for MetaTrader 5

MQL5 Programming Language Guide — Algorithmic Trading for MetaTrader 5

DodaTech Updated Jun 7, 2026 13 min read

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 .ex5 files that export reusable functions (marked with #property library and export keywords).
  • 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:

HandlerTriggerUsed In
OnInit()EA/indicator loaded on chartSetup: indicator handles, file opens, variable init
OnDeinit(const int reason)EA/indicator removedCleanup: release handles, close files
OnTick()New price tick arrivesCore trading logic for EAs
OnTimer()Timer interval expiresPeriodic tasks (email reports, resets)
OnChartEvent(...)Chart events: clicks, key presses, objectsInteractive panels & custom GUI
OnTrade()Trade transaction occursPost-trade logging, position monitoring
OnCalculate(...)New bar or tick for indicatorsIndicator calculation

Trading Functions

MQL5 uses a request/result pattern for all trading operations. You populate a MqlTradeRequest struct, call OrderSend(), and inspect the MqlTradeResult:

FunctionPurpose
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:

FunctionIndicatorReturn
iMA(symbol, period, ma_period, shift, method, applied)Moving AverageHandle
iRSI(symbol, period, rsi_period, applied)Relative Strength IndexHandle
iBollingerBands(symbol, period, bands_period, shift, dev, applied)Bollinger BandsHandle
iMACD(symbol, period, fast, slow, signal, applied)MACDHandle
iATR(symbol, period, atr_period)Average True RangeHandle
iStochastic(symbol, period, K, D, slowing, method, price)Stochastic OscillatorHandle
iADX(symbol, period, adx_period)Average Directional IndexHandle

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

FeatureMQL5MQL4
ParadigmObject-orientedProcedural / limited OOP
Trade modelPosition-orientedOrder-oriented
Trade functionsPositionOpen, PositionCloseOrderSend, OrderClose
Event handlersOnInit, OnTick, OnDeinitinit, start, deinit
Indicator handlesHandle-based (iMA returns handle)Call-based (iMA returns value directly)
Multi-currency testerYesNo
Timeframes21 timeframes9 timeframes
ONNX / Python bridgeBuilt-inNot supported
Compiler32-bit floats, 64-bit integers64-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

  1. 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.

  2. How do you read an indicator value in MQL5? Create a handle with iMA() (or iRSI(), etc.), then call CopyBuffer(handle, 0, 0, count, array) to copy values into a dynamic array.

  3. What is the difference between OrderSend in MQL5 vs MQL4? MQL5 uses a request/result pattern with MqlTradeRequest/MqlTradeResult structs; MQL4 uses direct parameters. MQL5 distinguishes positions from orders; MQL4 treats everything as orders.

  4. 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() returned INIT_FAILED because an indicator handle was invalid.

  5. How can I integrate a machine-learning model with MQL5? Export your model from Python to ONNX format, then use OnnxCreateFromBuffer() and OnnxRun() 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

What is MQL5 used for?
MQL5 is used to write Expert Advisors (automated trading robots), custom indicators, scripts, and services for the MetaTrader 5 platform — the world’s most popular retail forex and CFD trading terminal.
Do I need to know C++ first?
Not necessarily. MQL5 syntax is C++-inspired, but simpler. If you know C++, you’ll feel right at home. If you’re coming from Python, the concepts transfer — the syntax just looks different.
Can MQL5 trade cryptocurrencies?
Yes — MetaTrader 5 supports crypto CFDs through many brokers, and MQL5 EAs can trade them just like forex pairs. Use _Symbol to reference the crypto instrument.
Is MQL5 better than MQL4 for new projects?
Yes. MQL5 is actively developed, supports multi-currency testing, has modern OOP features, and includes capabilities like ONNX model inference that MQL4 lacks. Use MQL5 for all new development.
How do I debug an EA when it doesn’t trade?
Print key values with Print(), check the Experts log, use the built-in debugger with breakpoints, and run the EA in the Strategy Tester on a single tick to step through execution.
Can I connect MQL5 to external APIs?
Indirectly — MQL5 has WebRequest() for HTTP(S) calls, which can feed external signals or data into your EA. You can also use socket connections through custom DLL imports.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro