Home › Guides › Trading bots & code
MQL5 for beginners: your first Expert Advisor, line by line
MQL5 is the language of MetaTrader 5's Expert Advisors. It looks scarier than it is. This page builds one small EA in 4 blocks — and the safety parts come before the trading part.
What you'll build
A small Expert Advisor (EA) for MetaTrader 5 that:
- Waits for a new candle, so it decides once per candle, not on every price tick.
- Checks a simple rule: a fast EMA crossing above a slow one on the last finished candle.
- Sizes the trade so a stop-out costs about 1% of the account.
- Sends the order with a stop and a target attached.
- Stops trading for the day after a 2% drop.
The rule is deliberately plain. The point is the structure, not the signal. A crossing average is not an edge by itself.
Example code for learning — test it on paper first; it is not a ready-made trading system.
Before you start
- Install MetaTrader 5 from your broker and open a demo account (fake money).
- Open MetaEditor (the code editor that comes with it; F4 on Windows).
- Create a new Expert Advisor from its menu. MetaEditor makes a starter file ending in .mq5. Clear it and type the blocks below, in order, in that one file.
New to EAs? Read MetaTrader Expert Advisor basics first.
Block 1: Settings and setup
#include <Trade/Trade.mqh>
input int FastPeriod = 9; // fast EMA length
input int SlowPeriod = 21; // slow EMA length
input double RiskPercent = 1.0; // risk per trade, % of equity
input int StopPoints = 300; // stop distance, in points
input double MaxDailyLossPct = 2.0; // no new trades after this daily drop
CTrade trade;
int fastHandle, slowHandle;
datetime lastBar = 0;
double dayStartEquity = 0;
int lastDay = -1;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
}
In plain words:
- #include loads MetaTrader's ready-made trade helper, CTrade. It makes sending orders one line.
- Each input becomes a setting you can change in the EA's window without touching code. The text after // is a comment for humans.
- OnInit runs once when you attach the EA. iMA creates the two moving averages and gives back a "handle" (a ticket to read them later). If either fails, the EA refuses to start.
- OnDeinit runs when you remove the EA and frees the indicators.
- _Symbol and _Period mean "this chart's symbol and timeframe".
A point is the smallest price step for the symbol. On a 5-digit EUR/USD quote, 300 points = 30 pips. Check your symbol — points differ between markets.
Block 2: Size from the stop
double LotsForRisk(double stopDistance)
{
double riskMoney = AccountInfoDouble(ACCOUNT_EQUITY) * RiskPercent / 100.0;
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
if(tickValue <= 0 || tickSize <= 0 || step <= 0)
return(0);
double lossPerLot = stopDistance / tickSize * tickValue;
double lots = MathFloor(riskMoney / lossPerLot / step) * step;
if(lots < minLot)
return(0);
int digits = (int)MathRound(-MathLog10(step));
return(NormalizeDouble(MathMin(lots, maxLot), digits));
}
In plain words:
- riskMoney is 1% of current equity — the most one stop-out should cost.
- tickValue and tickSize come from the broker: how much money one price step is worth for 1 lot. That's how the EA works for any symbol.
- lossPerLot is what 1 lot would lose if the stop is hit.
- lots is risk ÷ loss per lot, rounded down to the broker's lot step. Rounding down keeps risk at or under 1%.
- If the answer is below the smallest lot allowed, it returns 0 — meaning "the stop is too wide for this risk; skip the trade". It never rounds up to make a trade fit.
Example: 10,000 equity, 1% risk = 100. If 1 lot loses 300 at the stop, 100 ÷ 300 = 0.333, rounded down to 0.33 lots. A math example, not a promise — a gap past the stop or a wider spread can cost more. Check it with our lot size calculator.
Block 3: The daily loss limit
bool DailyLimitHit()
{
MqlDateTime now;
TimeToStruct(TimeCurrent(), now);
if(now.day_of_year != lastDay)
{
lastDay = now.day_of_year;
dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
}
return(AccountInfoDouble(ACCOUNT_EQUITY) <= dayStartEquity * (1.0 - MaxDailyLossPct / 100.0));
}
In plain words: when a new day starts (by the broker's server clock), remember the equity. After that, if equity falls 2% or more below that number, answer "yes, the limit is hit". The trading block checks this first and does nothing if it's true.
This is a kill switch written into the code. The other kill switch is the Algo Trading button on the toolbar — know where it is. See daily loss limit.
Block 4: The trading rule
void OnTick()
{
if(DailyLimitHit())
return;
datetime barTime = iTime(_Symbol, _Period, 0);
if(barTime == lastBar)
return;
lastBar = barTime;
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
if(CopyBuffer(fastHandle, 0, 1, 2, fast) < 2 || CopyBuffer(slowHandle, 0, 1, 2, slow) < 2)
return;
bool crossedUp = fast[1] <= slow[1] && fast[0] > slow[0];
if(!crossedUp || PositionSelect(_Symbol))
return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double stop = NormalizeDouble(ask - StopPoints * _Point, _Digits);
double target = NormalizeDouble(ask + 2 * StopPoints * _Point, _Digits);
double lots = LotsForRisk(StopPoints * _Point);
if(lots <= 0)
{
Print("Stop too wide for the risk limit. Skipped.");
return;
}
if(trade.Buy(lots, _Symbol, ask, stop, target))
Print("Buy sent: ", lots, " lots, stop ", stop, ", target ", target);
else
Print("Buy failed: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());
}
Line by line, in plain words:
- OnTick runs on every new price. First stop: the daily limit.
- iTime(..., 0) is the open time of the current candle. If it's the same as last time, this isn't a new candle — do nothing. So the EA decides once per candle.
- CopyBuffer(handle, 0, 1, 2, ...) copies 2 values of each average, starting from candle 1 — the last finished candle. Candle 0 is still forming and can change. Skipping it avoids acting on a cross that later disappears.
- ArraySetAsSeries makes [0] the newest value and [1] the one before.
- crossedUp is true when the fast average was at or below the slow one, and is now above it.
- PositionSelect(_Symbol) is true if there's already a trade on this symbol. One trade at a time.
- stop and target are set from the entry price: stop 300 points below, target 600 above. NormalizeDouble rounds them to the symbol's price digits.
- trade.Buy sends the order with the stop and target attached, so they sit at the broker — even if your computer turns off.
- Print writes every result to the Experts log, including failures with the broker's reason code. Read that log.
Compile, test, then demo
- Click Compile in MetaEditor. Fix any errors it lists (usually a missing semicolon or bracket).
- Open the Strategy Tester in MetaTrader 5, pick your EA, a symbol and a long date range, and run it.
- Read the trades, not just the final number: the longest losing streak and the deepest Drawdown.
- Run it on a demo account for weeks. See paper trading a bot.
Two test traps: overfitting (tuning the settings until the past looks perfect — then it fails on new data) and unrealistic fills (ignoring Spread and Slippage). A Strategy Tester result is hypothetical. Losses are always possible.
This EA is a skeleton, not a strategy. It shows the safe structure: decide on finished candles, size from the stop, attach the stop, cap the day, log everything. The rule inside is yours to replace with one you've written and tested.
What this EA doesn't do (yet)
- Sells. Add the mirror rule with trade.Sell, stop above, target below.
- Broker stop limits. Some brokers require a minimum stop distance. Check SYMBOL_TRADE_STOPS_LEVEL before sending.
- Hedging accounts. If your account allows several positions per symbol, or runs other EAs, track this EA's own trades with a "magic number" (trade.SetExpertMagicNumber).
- News filters and trading hours. Add them as more checks at the top of OnTick.
More ideas: trading bot risk controls and common trading bot mistakes.
Questions
Is MQL5 hard to learn?
It's harder than Python because it's strict like C++, with types, semicolons and brackets. But a small EA is under 100 lines, and MetaEditor shows errors with line numbers.
What is the difference between MQL4 and MQL5?
MQL4 runs on MetaTrader 4 and MQL5 on MetaTrader 5. They look similar, but trading functions and indicator handling differ, so code has to be rewritten to move between them.
How do I backtest an Expert Advisor in MT5?
Compile it in MetaEditor, open the Strategy Tester in MetaTrader 5, choose the EA, symbol, dates and modelling mode, then run it and read the report and trade list.
Can I learn MQL5 without knowing how to code?
Yes, but start small: copy one block, compile it, change one input, compile again. Learning the basic ideas in a simpler language like Python first can also help.
Want the next step? We'll walk you through it.
Four free lessons by email, one every few days — each with a 10-minute exercise on a real chart.
Send Me the Free Lessons 4 short emails · No spam · Unsubscribe anytimeOr see the whole method, step by step:
See What’s Inside the Course Try everything free first. Decide when you're ready. · Open the free chart tool (works in the TradingView phone app)One chart idea a day, in plain English. Follow along: