MQL4 Programming Language Guide — Expert Advisors for MetaTrader 4
MQL4 (MetaQuotes Language 4) is the programming language for the MetaTrader 4 trading platform — a C-like scripting language used to create Expert Advisors, custom indicators, and scripts for automated forex and CFD trading. In this tutorial, you’ll learn the MQL4 program model, its trade functions, and why migrating to MQL5 is the recommended path for new projects.
Why it matters: MetaTrader 4 still holds a massive share of the retail forex market. Thousands of EAs and indicators on the MQL4 Marketplace generate real trading income every day. Understanding MQL4 lets you maintain legacy systems, migrate them to MQL5, and tap into a vast library of existing trading tools.
Real-world use: Signal providers run MQL4 EAs on VPS servers to auto-copy trades to hundreds of subscriber accounts. Brokers offer MT4-compatible servers specifically because of the ecosystem of existing MQL4 indicators and EAs.
What You’ll Learn
- MQL4 basics: syntax, program types, and execution model
- Event handlers:
init,deinit,start, and tick processing - Trade functions:
OrderSend,OrderSelect,OrderModify,OrderClose - MQL4 vs MQL5 differences and migration path
- Common pitfalls when writing MQL4 EAs
- When to stay on MQL4 and when to migrate
Learning Path
flowchart LR
A[MQL4 Basics<br/>You are here] --> B[Program Types & Event Handlers]
B --> C[Trade Functions & Indicators]
C --> D{Should I Migrate?}
D -->|Yes| E[MQL5 Migration Path]
D -->|No| F[Optimize Existing MQL4 EA]
What Is MQL4?
MQL4 uses a C-like syntax with limited object-oriented support (you get structures and some built-in classes, but no user-defined classes with inheritance). The language is interpreted at runtime by the MT4 client terminal. Compiled MQL4 programs produce .ex4 files.
The key distinction from MQL5: MQL4 is order-oriented — every trade operation works with an “order” (which can be a market position, a pending order, or even a closed trade). MQL5 separates these into distinct concepts (positions, orders, deals).
MQL4 Program Types
| Type | Purpose | Entry Point |
|---|---|---|
| Expert Advisor | Automated trading bot | start() |
| Custom Indicator | Custom chart indicator | start() |
| Script | One-time execution | start() |
Unlike MQL5, MQL4 has no Service or Library program types. Include files (.mqh) exist but are text-only — there is no compiled library format.
Event Handlers
int init() {
// Called once when EA is loaded onto a chart
return(0);
}
int deinit() {
// Called once when EA is removed from the chart
return(0);
}
int start() {
// Called on every new tick — the core trading function
return(0);
}| Handler | Trigger |
|---|---|
init() | EA loaded onto chart |
deinit() | EA removed from chart |
start() | Every new tick (equivalent to MQL5’s OnTick) |
Trade Functions
MQL4’s trade functions are direct-call — you pass parameters directly rather than using a request struct:
| Function | Purpose |
|---|---|
OrderSend(symbol, cmd, volume, price, slippage, sl, tp, comment, magic, expiration, arrow) | Open a new order |
OrderSelect(index, select, pool) | Select an order for reading/modification |
OrderModify(ticket, price, sl, tp, expiration, arrow) | Modify SL, TP, or price of an open order |
OrderClose(ticket, volume, price, slippage, arrow) | Close a market order |
OrderDelete(ticket) | Delete a pending order |
OrderTicket() | Return ticket of selected order |
OrderLots() / OrderOpenPrice() / etc. | Read properties of selected order |
Example — Simple Moving Average EA
// Simple SMA Crossover EA for MQL4
extern double LotSize = 0.1;
extern int FastMA = 10;
extern int SlowMA = 30;
extern int Magic = 2024;
int init() {
return(0);
}
int deinit() {
return(0);
}
int start() {
double fastNow = iMA(NULL, 0, FastMA, 0, MODE_SMA, PRICE_CLOSE, 0);
double fastPrev = iMA(NULL, 0, FastMA, 0, MODE_SMA, PRICE_CLOSE, 1);
double slowNow = iMA(NULL, 0, SlowMA, 0, MODE_SMA, PRICE_CLOSE, 0);
double slowPrev = iMA(NULL, 0, SlowMA, 0, MODE_SMA, PRICE_CLOSE, 1);
int total = OrdersTotal();
bool hasPosition = false;
for(int i = 0; i < total; i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() == Magic && OrderSymbol() == Symbol()) {
hasPosition = true;
break;
}
}
}
if(!hasPosition) {
if(fastNow > slowNow && fastPrev <= slowPrev) {
OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "", Magic, 0, Green);
}
if(fastNow < slowNow && fastPrev >= slowPrev) {
OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "", Magic, 0, Red);
}
}
return(0);
}Expected behavior: On each tick, the EA calculates two SMAs. If the fast MA crosses above the slow MA and there is no existing position with the same Magic number, it sends a market buy order. The cross in the opposite direction triggers a sell. Note that in MQL4, iMA() returns the indicator value directly — there is no handle or CopyBuffer pattern.
Example — Close All Orders Script
// Close all open orders for this symbol
int start() {
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol()) {
if(OrderType() == OP_BUY)
OrderClose(OrderTicket(), OrderLots(), Bid, 3, Red);
if(OrderType() == OP_SELL)
OrderClose(OrderTicket(), OrderLots(), Ask, 3, Red);
}
}
}
return(0);
}Expected behavior: Iterates all open orders in reverse, selects each one, and closes market orders at the current price. Pending orders are left untouched.
MQL4 vs MQL5 — Migration Path
flowchart TD
MQL4[MQL4 Code] --> Analyze{Analyze usage}
Analyze -->|OrderSend/ OrderClose| Positions[Map to MQL5<br/>PositionOpen, PositionClose]
Analyze -->|iMA returns value| Handles[Map to handle-based<br/>iMA + CopyBuffer]
Analyze -->|start()| Events[Map to OnTick]
Analyze -->|extern| Inputs[Map to input]
Analyze -->|No classes| OOP[Refactor to classes]
Positions --> Test
Handles --> Test
Events --> Test
Inputs --> Test
OOP --> Test
Test[Backtest in MQL5<br/>Strategy Tester] --> Result{Matching results?}
Result -->|No| Debug[Debug & Re-run]
Debug --> Test
Result -->|Yes| Done[Migration Complete]
Key Migration Table
| MQL4 | MQL5 Equivalent |
|---|---|
int start() | void OnTick() |
int init() | int OnInit() |
int deinit() | void OnDeinit(int reason) |
iMA(NULL,0,14,0,MODE_SMA,PRICE_CLOSE,0) | Create handle → CopyBuffer(handle,0,0,1,buf) |
OrderSend(Symbol(),OP_BUY,0.1,Ask,3,0,0,"",magic,0,Red) | MqlTradeRequest + OrderSend(req, res) |
OrdersTotal() + OrderSelect() loop | PositionSelect() or PositionsTotal() |
extern double x; | input double x; |
| No classes | Full OOP: classes, inheritance, polymorphism |
| 9 timeframes | 21 timeframes |
| Single-currency tester | Multi-currency tester |
Why MQL5 Is Recommended for New Projects
MetaQuotes has frozen MQL4 development. All new features — ONNX integration, multi-currency backtesting, 64-bit compilation, custom graphics — land in MQL5 first (or exclusively). The MQL5 compiler produces faster, more reliable code, and the backtester is significantly more accurate with “Every tick” modelling.
You should migrate when:
- You need multi-currency or multi-asset strategies
- You want to use machine learning (ONNX) in your EA
- You’re building a commercial product (MQL5 Marketplace is the active marketplace)
- Your MQL4 EA has become too complex to maintain without OOP
You can stay on MQL4 when:
- You maintain legacy EAs that work reliably
- Your broker only offers MT4 (no MT5)
- You use third-party MQL4-only tools (signal copiers, custom indicators)
Common Errors
1. OrderSend returns -1 (error)
The most common error is incorrect price. Always use Ask for buys and Bid for sells. Slippage set too low (e.g., 0) can also cause rejection — use 3–5 pips for forex.
2. OrderSelect fails after OrderSend
OrderSend changes the order pool. Always re-select the order after sending if you need to read its properties. Use the ticket returned by OrderSend.
3. iMA() returns 0 or wrong value
Check that the symbol and timeframe arguments are valid. NULL means “current chart symbol”, 0 means “current timeframe”. The last parameter is the shift — index 0 is current bar (which may be forming), 1 is the completed bar.
4. Loop modifies OrdersTotal() during iteration
When closing or deleting orders inside a loop, the total changes. Always iterate backwards (for(int i=OrdersTotal()-1; i>=0; i--)) to avoid index skipping.
5. Magic number collision
Every EA should have a unique Magic number to identify its own orders. Without it, multiple EAs on the same symbol will interfere with each other’s positions.
Practice Questions
What is the entry-point function for an MQL4 Expert Advisor?
start()— it fires on every new tick. Equivalent toOnTick()in MQL5.How does MQL4’s
iMA()differ from MQL5’siMA()? In MQL4,iMA()returns the indicator value directly (e.g.,iMA(NULL,0,14,0,MODE_SMA,PRICE_CLOSE,0)). In MQL5,iMA()returns a handle which you then pass toCopyBuffer()to get values.Why should I loop backwards through
OrdersTotal()? When you close or delete an order, the order count decreases and higher-indexed orders shift down. A forward loop skips orders; a backward loop avoids this.What is a Magic number and why do I need one? A Magic number is a unique identifier assigned to an EA’s orders. It prevents one EA from accidentally closing another EA’s positions on the same symbol.
How do I convert an MQL4 indicator call to MQL5? Replace the direct
iMA(... shift)call with: (1) create a handle inOnInit()withiMA(...), (2) copy values inOnTick()withCopyBuffer(handle, 0, 0, count, array), (3) usearray[0]for the current bar.
Challenge: Take an existing MQL4 EA you’ve written or found, identify every MQL4-only function call (OrderSend with direct params, iMA with shift, start()), and write the equivalent MQL5 version. Run both in their respective Strategy Testers and compare the equity curves.
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