MT5 Expert Advisor Tutorial: Complete Beginner Guide to MQL5 EAs 2026
Build and inspect a chart-timeframe moving-average crossover Expert Advisor. This beginner guide connects MetaEditor setup, indicator handles, completed-bar signals and checked trade requests. The sample is for testing and does not establish compatibility with every symbol, account or broker.
Create the file and trace its lifecycle
In MetaEditor, create an Expert Advisor template, save this source in MQL5/Experts and compile it. OnInit creates the handles once; OnTick processes chart-symbol updates; OnDeinit releases handles when the program is reinitialized or unloaded.
- Inspect diagnostics before attaching to a demo chart.
- Choose the chart symbol and timeframe deliberately: _Symbol and _Period drive this example.
- A compiled EX5 is an executable program, not evidence that trading decisions match your requirements.
Verify one crossover before enabling execution
Use Strategy Tester with recorded dates, inputs and modeling settings. At a chosen crossover, compare buffer values for bars 2 and 1 with the chart, and inspect order logs. Repeat after changing the code.
- Test missing indicator data, rejected orders and failed reverse closes.
- Check fixed lots, stop distances and allowed filling modes against the selected symbol.
- Evaluate an independent period and demo behavior; neither a compiler pass nor a historical profit guarantees future results.
Implementation details & mechanics
Core implementation pattern
OnInit validates inputs and creates two iMA handles. OnTick reads three values into dynamic series arrays, then compares completed bars 2 and 1. A new-bar gate prevents another decision on the same chart bar after data is ready. Before opening, the EA checks both directions after any opposite-position close attempt.
Error handling & safety rules
Market orders request initial SL and TP at pip distances using the three/five-digit FX convention. Opposite crosses request closure; a failed close blocks the replacement entry and remains queued for later tick retries. Broker rejection is logged using the server return code. This is fixed-volume code, without a portfolio guard, trailing stop or restart-persistent pending-exit state.
MQL5 Code Implementation
//+------------------------------------------------------------------+
//| BeginnerEA_Tutorial.mq5 |
//| Step-by-step teaching example: MA Crossover Expert Advisor |
//| Pineify.app — AI-powered MQL5 & Pine Script code generation |
//+------------------------------------------------------------------+
#property copyright "Pineify.app"
#property link "https://pineify.app"
#property version "1.00"
#property strict
//--- Input parameters (tunable from the EA settings dialog)
input int FastMAPeriod = 10; // Fast MA period
input int SlowMAPeriod = 30; // Slow MA period
input ENUM_MA_METHOD MAMethod = MODE_EMA; // MA smoothing method
input ENUM_APPLIED_PRICE AppliedPrice = PRICE_CLOSE; // Applied price
input double LotSize = 0.10; // Trade lot size
input int MagicNumber = 123456; // Unique EA identifier
input int SlippagePoints = 10; // Maximum allowed slippage
input double StopLossPips = 50.0; // Stop-loss in pips
input double TakeProfitPips = 100.0; // Take-profit in pips
//--- Global variables
int fastMAHandle = INVALID_HANDLE;
int slowMAHandle = INVALID_HANDLE;
double fastMABuffer[];
double slowMABuffer[];
double pipValue;
bool closeBuys=false, closeSells=false;
//+------------------------------------------------------------------+
//| OnInit — called once when the EA is attached or reloaded |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Validate inputs
if(FastMAPeriod<=0 || SlowMAPeriod<=0 || FastMAPeriod >= SlowMAPeriod)
{
Print("ERROR: FastMAPeriod must be less than SlowMAPeriod.");
return INIT_PARAMETERS_INCORRECT;
}
if(LotSize<=0 || StopLossPips<=0 || TakeProfitPips<=0 || SlippagePoints<0 || MagicNumber<=0)
{
Print("ERROR: LotSize must be greater than zero.");
return INIT_PARAMETERS_INCORRECT;
}
//--- Create indicator handles (MT5 handle-based API)
fastMAHandle = iMA(_Symbol, _Period, FastMAPeriod, 0, MAMethod, AppliedPrice);
slowMAHandle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MAMethod, AppliedPrice);
if(fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE)
{
Print("ERROR: Failed to create MA indicator handles.");
return INIT_FAILED;
}
//--- Set buffer as series so index 0 = most recent bar
ArraySetAsSeries(fastMABuffer, true);
ArraySetAsSeries(slowMABuffer, true);
//--- Calculate pip value once (1 pip = 10 points for 5-digit brokers)
pipValue = _Point * 10.0;
if(Digits() == 3 || Digits() == 5)
pipValue = _Point * 10.0;
else
pipValue = _Point;
Print("BeginnerEA initialised. Symbol=", _Symbol,
" FastMA=", FastMAPeriod, " SlowMA=", SlowMAPeriod);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| OnDeinit — called when the EA is removed or terminal closes |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Release indicator handles to free memory
if(fastMAHandle != INVALID_HANDLE)
IndicatorRelease(fastMAHandle);
if(slowMAHandle != INVALID_HANDLE)
IndicatorRelease(slowMAHandle);
Print("BeginnerEA removed. Reason code: ", reason);
}
//+------------------------------------------------------------------+
//| OnTick — processes chart-symbol NewTick events |
//+------------------------------------------------------------------+
void OnTick()
{
if(closeBuys) { CloseAllPositions(POSITION_TYPE_BUY); closeBuys=CountPositions(POSITION_TYPE_BUY)>0; }
if(closeSells) { CloseAllPositions(POSITION_TYPE_SELL); closeSells=CountPositions(POSITION_TYPE_SELL)>0; }
if(closeBuys || closeSells) return;
//--- Only act on the open of a new bar (avoids re-entry on same bar)
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == lastBarTime)
return;
if(currentBarTime<=0) return;
//--- Copy three MA values into our buffers
if(CopyBuffer(fastMAHandle, 0, 0, 3, fastMABuffer) < 3 ||
CopyBuffer(slowMAHandle, 0, 0, 3, slowMABuffer) < 3)
{
Print("WARNING: Not enough data to read MA buffers yet.");
return;
}
lastBarTime=currentBarTime;
//--- Detect crossover signals
// Index 1 = previous closed bar, index 2 = bar before that
bool bullishCross = (fastMABuffer[2] <= slowMABuffer[2]) &&
(fastMABuffer[1] > slowMABuffer[1]);
bool bearishCross = (fastMABuffer[2] >= slowMABuffer[2]) &&
(fastMABuffer[1] < slowMABuffer[1]);
//--- Count open positions managed by this EA
int buyPositions = CountPositions(POSITION_TYPE_BUY);
int sellPositions = CountPositions(POSITION_TYPE_SELL);
//--- Act on bullish crossover: close sells, open buy
if(bullishCross)
{
CloseAllPositions(POSITION_TYPE_SELL);
closeSells=CountPositions(POSITION_TYPE_SELL)>0;
if(buyPositions == 0 && !closeSells)
OpenPosition(ORDER_TYPE_BUY);
}
//--- Act on bearish crossover: close buys, open sell
if(bearishCross)
{
CloseAllPositions(POSITION_TYPE_BUY);
closeBuys=CountPositions(POSITION_TYPE_BUY)>0;
if(sellPositions == 0 && !closeBuys)
OpenPosition(ORDER_TYPE_SELL);
}
}
//+------------------------------------------------------------------+
//| OpenPosition — sends a market order with SL and TP |
//+------------------------------------------------------------------+
void OpenPosition(ENUM_ORDER_TYPE orderType)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
double price, sl, tp;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(orderType == ORDER_TYPE_BUY)
{
price = ask;
sl = NormalizeDouble(price - StopLossPips * pipValue, _Digits);
tp = NormalizeDouble(price + TakeProfitPips * pipValue, _Digits);
}
else
{
price = bid;
sl = NormalizeDouble(price + StopLossPips * pipValue, _Digits);
tp = NormalizeDouble(price - TakeProfitPips * pipValue, _Digits);
}
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = LotSize;
request.type = orderType;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = SlippagePoints;
request.magic = MagicNumber;
request.comment = "BeginnerEA_Tutorial";
request.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(request, result) || result.retcode!=TRADE_RETCODE_DONE)
Print("OrderSend failed. RetCode=", result.retcode,
" Comment=", result.comment);
else
Print("Order opened. Ticket=", result.order,
" Type=", EnumToString(orderType), " Price=", price);
}
//+------------------------------------------------------------------+
//| CloseAllPositions — closes every position of the given type |
//+------------------------------------------------------------------+
void CloseAllPositions(ENUM_POSITION_TYPE posType)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != posType) continue;
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = PositionGetDouble(POSITION_VOLUME);
req.position = ticket;
req.deviation = SlippagePoints;
req.magic = MagicNumber;
req.comment = "BeginnerEA_Close";
req.type_filling = ORDER_FILLING_FOK;
if(posType == POSITION_TYPE_BUY)
{
req.type = ORDER_TYPE_SELL;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
}
else
{
req.type = ORDER_TYPE_BUY;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
}
if(!OrderSend(req, res) || res.retcode!=TRADE_RETCODE_DONE)
Print("Close failed. Ticket=", ticket, " RetCode=", res.retcode);
}
}
//+------------------------------------------------------------------+
//| CountPositions — returns open position count for given type |
//+------------------------------------------------------------------+
int CountPositions(ENUM_POSITION_TYPE posType)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == posType)
count++;
}
return count;
}
//+------------------------------------------------------------------+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.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Strategy orders are simulated; live automation needs a separate integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ.
Frequently Asked Questions
Related MQL5 pages
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.