P
PipsGrowth
Automation

How to Build Your First MT5 Expert Advisor

A step-by-step guide for beginners moving from manual trading to automated systems using MQL5.

PG
Pips Growth Team
2026-01-24
8 min

Automated trading removes emotion from the equation. Instead of second-guessing an entry or holding a loss "just a little longer," an Expert Advisor (EA) executes your rules without hesitation, every single tick, around the clock. If you've been trading manually and wondering how to cross into algorithmic trading, this guide walks you through the entire process — from installing the development environment to running a live backtest on a working strategy.

By the end, you'll have a complete, functional MQL5 EA built around a classic SMA crossover system, with proper stop loss, take profit, and position sizing built in.

What Is an Expert Advisor?

An Expert Advisor is a program written in MQL5 — MetaTrader 5's native scripting language — that runs directly inside the MT5 platform. It can monitor price, evaluate indicators, and place, modify, or close orders automatically without any manual input.

EAs are not magic. They automate a strategy you define. If your strategy has an edge, an EA will execute it more consistently than you can manually. If your strategy has no edge, the EA will lose money faster than you would manually. The code is only as good as the logic behind it.

There are three common types of automated programs in MT5:

Type Purpose
Expert Advisor Fully automated trading — opens and closes positions
Indicator Calculates and draws data on the chart
Script Runs once on demand — useful for batch actions

This guide focuses exclusively on Expert Advisors.

Setting Up MetaEditor

MetaEditor is the integrated development environment (IDE) bundled with every MetaTrader 5 installation. You do not need to download anything separately.

To open MetaEditor:

  1. Open MetaTrader 5
  2. Press F4, or go to Tools → MetaQuotes Language Editor
  3. MetaEditor opens in a new window

Once inside MetaEditor, navigate to File → New and select Expert Advisor (template). Give your EA a name — something like SMA_Crossover_EA. MetaEditor creates a file with the basic skeleton already in place: OnInit(), OnDeinit(), and OnTick().

Understanding MQL5 Structure

Every MQL5 EA is built around three core event handlers. Think of them as lifecycle hooks.

OnInit — Runs Once at Startup

This function fires when the EA first attaches to a chart, or when MT5 starts. Use it to validate inputs, initialize variables, and check that indicator handles are created successfully.

int OnInit()
{
   // Validate inputs
   if(InpLotSize <= 0 || InpStopLoss <= 0) {
      Print("Invalid input parameters");
      return INIT_PARAMETERS_INCORRECT;
   }
   return INIT_SUCCEEDED;
}

OnTick — Runs on Every Price Change

This is the main logic loop. It fires every time a new tick arrives — which can be dozens of times per second on active pairs. Keep this function efficient. Heavy calculations belong in OnInit or should be cached.

OnDeinit — Runs on Shutdown

Called when the EA is removed from the chart, or when MT5 closes. Use it to release indicator handles and clean up resources.

void OnDeinit(const int reason)
{
   IndicatorRelease(handleSMA50);
   IndicatorRelease(handleSMA200);
}

The SMA Crossover Strategy

The strategy used here is one of the oldest and most studied in technical analysis: the 50-period Simple Moving Average crossing the 200-period Simple Moving Average.

The rules are simple:

  • Buy when the SMA-50 crosses above the SMA-200 (golden cross)
  • Sell when the SMA-50 crosses below the SMA-200 (death cross)
  • Each position carries a fixed stop loss and take profit
  • Only one position open at a time

This is not a strategy meant to make you rich overnight. It is a clean, understandable framework for learning how to code — and it does have historical validity on higher timeframes like the daily chart.

The Complete EA Code

Below is a full, working implementation. Read through it carefully — every section is commented.

//+------------------------------------------------------------------+
//|  SMA Crossover EA — PipsGrowth Tutorial                          |
//|  Strategy: Buy on golden cross, Sell on death cross              |
//+------------------------------------------------------------------+
#property copyright "PipsGrowth"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

//--- Input parameters
input double InpLotSize    = 0.10;   // Lot size
input int    InpSMAFast    = 50;     // Fast SMA period
input int    InpSMASlow    = 200;    // Slow SMA period
input int    InpStopLoss   = 150;    // Stop loss in points
input int    InpTakeProfit = 300;    // Take profit in points
input int    InpMagicNum   = 112233; // Magic number

//--- Global variables
CTrade  trade;
int     handleFast;
int     handleSlow;

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Validate inputs
   if(InpLotSize <= 0 || InpStopLoss <= 0 || InpTakeProfit <= 0) {
      Print("ERROR: Invalid input parameters. EA will not run.");
      return INIT_PARAMETERS_INCORRECT;
   }

   //--- Create indicator handles
   handleFast = iMA(_Symbol, _Period, InpSMAFast, 0, MODE_SMA, PRICE_CLOSE);
   handleSlow = iMA(_Symbol, _Period, InpSMASlow, 0, MODE_SMA, PRICE_CLOSE);

   if(handleFast == INVALID_HANDLE || handleSlow == INVALID_HANDLE) {
      Print("ERROR: Failed to create indicator handles.");
      return INIT_FAILED;
   }

   //--- Configure trade object
   trade.SetExpertMagicNumber(InpMagicNum);
   trade.SetMarginMode();
   trade.SetTypeFillingBySymbol(_Symbol);

   Print("EA initialized successfully on ", _Symbol);
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   IndicatorRelease(handleFast);
   IndicatorRelease(handleSlow);
   Print("EA removed. Reason code: ", reason);
}

//+------------------------------------------------------------------+
//| Helper: Count open positions by magic number                     |
//+------------------------------------------------------------------+
int CountOpenPositions()
{
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == InpMagicNum) {
         count++;
      }
   }
   return count;
}

//+------------------------------------------------------------------+
//| Main tick function                                               |
//+------------------------------------------------------------------+
void OnTick()
{
   //--- Only trade on new bar (avoids multiple signals per bar)
   static datetime lastBarTime = 0;
   datetime currentBarTime = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   if(currentBarTime == lastBarTime) return;
   lastBarTime = currentBarTime;

   //--- Skip if a position is already open
   if(CountOpenPositions() > 0) return;

   //--- Retrieve indicator values for last 3 bars
   double fastBuffer[3], slowBuffer[3];
   if(CopyBuffer(handleFast, 0, 0, 3, fastBuffer) < 3) return;
   if(CopyBuffer(handleSlow, 0, 0, 3, slowBuffer) < 3) return;

   //--- Index 0 = current (incomplete) bar, index 1 = last closed bar
   double fastCurrent  = fastBuffer[1];
   double fastPrevious = fastBuffer[2];
   double slowCurrent  = slowBuffer[1];
   double slowPrevious = slowBuffer[2];

   //--- Detect crossovers
   bool goldenCross = (fastPrevious <= slowPrevious) && (fastCurrent > slowCurrent);
   bool deathCross  = (fastPrevious >= slowPrevious) && (fastCurrent < slowCurrent);

   //--- Current prices
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

   //--- Execute BUY on golden cross
   if(goldenCross) {
      double sl = ask - InpStopLoss * point;
      double tp = ask + InpTakeProfit * point;
      if(trade.Buy(InpLotSize, _Symbol, ask, sl, tp, "SMA Crossover BUY")) {
         Print("BUY opened at ", ask, " | SL: ", sl, " | TP: ", tp);
      } else {
         Print("BUY failed. Error: ", GetLastError());
      }
   }

   //--- Execute SELL on death cross
   if(deathCross) {
      double sl = bid + InpStopLoss * point;
      double tp = bid - InpTakeProfit * point;
      if(trade.Sell(InpLotSize, _Symbol, bid, sl, tp, "SMA Crossover SELL")) {
         Print("SELL opened at ", bid, " | SL: ", sl, " | TP: ", tp);
      } else {
         Print("SELL failed. Error: ", GetLastError());
      }
   }
}
//+------------------------------------------------------------------+

Key Code Concepts Explained

The CTrade class is part of the MQL5 standard library and provides clean, reliable methods for order execution. Using trade.Buy() and trade.Sell() is far safer than the raw OrderSend() function — it handles slippage, filling modes, and error checking internally.

The new-bar check (lastBarTime) prevents the EA from firing multiple times on the same candle. Without it, the logic would re-evaluate every tick, potentially opening duplicate trades.

CopyBuffer() fills an array with indicator values. Index 0 is the current (still-forming) bar. Index 1 is the last fully closed bar. Always use index 1 and 2 for crossover detection — using index 0 gives false signals on partially formed candles.

Point vs Pip: In MQL5, SYMBOL_POINT returns the minimum price increment. On a 5-digit broker, one pip = 10 points. If you set InpStopLoss = 150, that equals 15 pips on a 5-digit pair. Adjust accordingly.

Compiling Your EA

In MetaEditor, press F7 to compile. If there are errors, they appear in the bottom Errors panel with the line number highlighted. Common first-time errors:

Error Cause Fix
'CTrade' — undefined Missing include Add #include <Trade\Trade.mqh>
undeclared identifier Variable used before declaration Declare variable at the top
cannot convert Wrong data type Cast explicitly using (int) or (double)
division by zero Math operation with zero denominator Add a guard condition

A successful compile shows 0 errors, 0 warnings in the toolbar.

Backtesting in Strategy Tester

Before putting any real money at risk, test your EA on historical data. MT5's Strategy Tester is powerful enough to simulate years of price action in seconds.

  1. In MT5, press Ctrl+R to open Strategy Tester
  2. Select your EA from the Expert Advisor dropdown
  3. Set the Symbol and Timeframe (start with EURUSD, Daily)
  4. Set the Date range — at least 3–5 years of history
  5. Choose Every tick based on real ticks for the most accurate simulation
  6. Click Start

When the test finishes, review these metrics in the Results tab:

  • Profit Factor: Total gross profit divided by total gross loss. Above 1.5 is generally acceptable.
  • Max Drawdown: The largest peak-to-trough loss. High drawdown means high risk.
  • Win Rate: Less important than profit factor, but useful context.
  • Total trades: Too few trades means results are statistically unreliable.

A profit factor of 1.8 on the Daily EURUSD chart over 5 years is a reasonable starting point. But backtesting alone is never enough — always forward-test on a demo account for at least 2–3 months before trading live.

Optimization Tips

The Strategy Tester's Optimization mode lets you test multiple parameter combinations automatically. Right-click on any input parameter and check the boxes to include it in the optimization pass.

Common parameters to optimize:

  • SMA periods (try fast: 20–80, slow: 100–250)
  • Stop loss and take profit levels

Warning: Over-optimization, also called "curve fitting," produces an EA that looks perfect on historical data but fails in live trading. Look for parameter ranges where results are consistently good, not a single peak surrounded by losses.

Common Beginner Mistakes

Not normalizing prices: Always use NormalizeDouble(price, _Digits) when calculating stop loss and take profit levels. Unnormalized prices cause order rejection errors.

Trading on the current bar: Using fastBuffer[0] for crossover detection reads a bar that hasn't closed yet, generating false signals every tick. Always reference index 1 (the last closed bar).

Ignoring spread costs: Backtesting with zero spread makes every strategy look profitable. Set a realistic spread in Strategy Tester to model actual trading costs.

No magic number: If you run multiple EAs on the same account, each must have a unique magic number. Without it, position-counting logic breaks and EAs may interfere with each other.

For a deeper understanding of the MT5 platform and its capabilities, see our MetaTrader 5 Complete Guide. Once your EA is producing consistent backtest results, the next step is rigorous historical analysis — covered in our Strategy Backtesting Guide.

Frequently Asked Questions

Do I need programming experience to build an EA in MQL5?

Basic programming knowledge helps, but it is not a strict requirement to get started. MQL5 is syntactically close to C++, so anyone who has coded in C, C++, or Java will feel at home quickly. If you have no programming background, spend a week learning variables, conditionals (if/else), and loops — that is enough to understand and modify simple EAs like the one in this guide.

Can I run this EA on a real money account immediately after backtesting?

No. Backtesting shows how a strategy would have performed on historical data, but past performance does not guarantee future results. After backtesting, run the EA on a demo account in real-time for at least two to three months. Watch how it behaves during news events, low-liquidity periods, and trending vs ranging markets before committing real capital.

Why does my EA open orders with the wrong lot size or no stop loss?

The most common cause is a broker's minimum lot size constraint or a stop level restriction. Brokers enforce a minimum distance between the entry price and stop loss. If your calculated stop loss is too close to the current price, the order is rejected. Check SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) to find the minimum stop distance, and add a buffer to your calculations.

How many trades should I see in a backtest for the results to be meaningful?

Most statisticians recommend a minimum of 100 trades for results to carry statistical weight. Fewer than 30 trades and the profit factor means almost nothing — you could get the same result by random chance. If your EA only fires a handful of signals per year on the Daily chart, switch to H4 or H1 to generate more test data.

What is the difference between iMA() and CopyBuffer()?

iMA() creates an indicator handle — essentially a pointer that tells MT5 which indicator to compute and on which settings. CopyBuffer() is what you call each tick to retrieve the actual calculated values from that handle. You create the handle once in OnInit() and call CopyBuffer() as many times as needed in OnTick(). Never call iMA() repeatedly inside OnTick() — it creates a new handle each time and leaks memory.

Is the SMA crossover strategy actually profitable in live trading?

The SMA crossover is a trend-following strategy. It performs well in strongly trending markets and poorly in sideways, choppy conditions — which describes the majority of market time. On its own, the raw version in this guide is not designed to be a live trading system. It is a teaching tool. Real traders use crossovers as one component of a larger system that includes trend filters, volatility filters, and strict position sizing. Use it to learn coding, then evolve it into something more robust.

Affiliate Disclosure: This page contains affiliate links. We may earn a commission when you click on certain links or sign up with brokers featured on this site, at no additional cost to you. Our reviews and recommendations are based on thorough research and remain unbiased.Learn more

Risk Warning: Trading forex and CFDs involves significant risk of loss. Past performance is not indicative of future results. CFDs are complex instruments and come with a high risk of losing money rapidly due to leverage. Ensure you understand the risks before trading. This content is for educational purposes only and does not constitute financial advice.

Written by

Pips Growth Team

Trading Education & Research Team

The Pips Growth Team is a group of experienced traders, financial analysts, and trading educators dedicated to providing accurate, actionable forex education. Our team combines decades of hands-on market experience with deep technical knowledge to create comprehensive guides, honest broker reviews, and proven trading strategies. Every article is thoroughly researched, fact-checked, and reviewed by multiple team members to ensure the highest quality and accuracy.

15+ Years Experience43+ Articles Published
Forex & CFD TradingTechnical AnalysisRisk ManagementBroker Reviews & Evaluation
View Full Profile
Share

Get Trading Tips

How to Build Your First MT5 Expert Advisor | PipsGrowth