MetaTrader 5 is a trading terminal used by many retail traders to view charts, place orders, run indicators, and test automated systems.
This article is not a promotion for MetaTrader. The platform is used here for a practical reason: many retail traders already have access to it through their brokers, and it includes a built-in programming environment for automation.
That programming language is MQL5.
MQL5 is used to create programs that run inside MetaTrader 5. These programs can read market prices, process candle data, calculate indicators, manage orders, and test trading rules on historical data.
The main type of program we will use in this series is the Expert Advisor.
An Expert Advisor, usually called an EA, is a program attached to a chart. It waits for events from the terminal. When a new price update arrives, the EA can run code.
That makes it useful for testing trading rules in a controlled way.
The goal of this first article is simple. We are going to understand the structure of an EA, read basic market data, display information on the chart, and finish with a simple strategy that can be automated.
This article does not try to prove that automation is better than manual trading. It only shows how a trading rule can be written in code.
MetaTrader, MetaEditor, and MQL5
MetaTrader 5 has several parts.
The terminal is the main application. This is where charts, symbols, orders, positions, and account information are shown.
MetaEditor is the code editor. This is where MQL5 files are written and compiled.
The Strategy Tester is used to run an Expert Advisor on historical data. It allows the trader to see how a rule would have behaved under past market conditions.
MQL5 is the language that connects these parts. It gives access to price data, account data, trade functions, indicators, and chart information.
In this series, we will mostly write Expert Advisors.
An EA is useful because it forces the trader to define exact rules.
A manual idea may sound clear in conversation, but code requires more precision. The program needs to know when to act, what data to read, what condition to check, and what to do after the condition is true.
That is the main value of writing trading logic in MQL5.
It removes vague instructions.
The basic Expert Advisor structure
A simple EA usually contains three important functions.
OnInit()runs when the EA starts.OnDeinit()runs when the EA stops.OnTick()runs when a new price update arrives.
Here is a minimal Expert Advisor.
#property strict
int OnInit()
{
Print("Expert Advisor started.");
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
Print("Expert Advisor stopped.");
}
void OnTick()
{
Print("New tick received.");
}This program does not trade. It only writes messages to the terminal log.
That is enough for a first example because it shows the event structure.
When the EA is attached to a chart,
OnInit()runs once.When the price updates,
OnTick()runs.When the EA is removed,
OnDeinit()runs.
The most important function for now is OnTick().
Most beginner trading logic starts there.
Reading the current price
The EA needs market data before it can make any decision.
The simplest data to read is the current bid and ask price.
The bid is the price used for selling.
The ask is the price used for buying.
This example reads both and displays them on the chart.
#property strict
void OnTick()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
Comment(
"Symbol: ", _Symbol, "\n",
"Bid: ", DoubleToString(bid, _Digits), "\n",
"Ask: ", DoubleToString(ask, _Digits)
);
}_Symbol refers to the chart symbol.
If the EA is attached to EURUSD, _Symbol is EURUSD.
If the EA is attached to XAUUSD, _Symbol is XAUUSD.
SYMBOL_BID asks MetaTrader for the current bid price.
SYMBOL_ASK asks MetaTrader for the current ask price.
_Digits tells the program how many decimal places the current symbol uses.
Comment() writes text directly on the chart.
This is still not a trading system. It is only a small market-data monitor.
That is the right place to start.
Before an EA trades, it should read and display data correctly.
Working with candles
A tick is a price update.
A candle is a summary of price movement over a selected timeframe.
An EA attached to a chart may receive many ticks while one candle is forming.
That matters because code inside OnTick() can run many times during the same candle.
For many beginner strategies, it is cleaner to check conditions once per new candle.
This small function detects a new candle.
datetime lastBarTime = 0;
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
return true;
}
return false;
}It can be used inside OnTick() like this.
void OnTick()
{
if(IsNewBar())
{
Print("New candle detected.");
}
}iTime() returns the opening time of a candle.
The value 0 refers to the current candle.
When that time changes, a new candle has started.
This gives the EA a simple rhythm.
The program still receives every tick, but it can choose to run the main logic only when a new candle begins.
Reading the previous candle
Many strategies use completed candles.
That is usually cleaner than using the current candle, because the current candle is still changing.
This example reads the high and low of the previous completed candle.
#property strict
void OnTick()
{
double previousHigh = iHigh(_Symbol, _Period, 1);
double previousLow = iLow(_Symbol, _Period, 1);
Comment(
"Previous candle high: ", DoubleToString(previousHigh, _Digits), "\n",
"Previous candle low: ", DoubleToString(previousLow, _Digits)
);
}The value 1 refers to the previous candle.
The value 0 would refer to the current candle.
For entry rules, the previous candle is often easier to work with because its values are fixed.
Adding inputs
Hard-coded values make testing slow.
MQL5 allows settings to be exposed as inputs. These appear in the EA settings window when the program is attached to a chart or loaded in the Strategy Tester.
input double Lots = 0.10;
input int StopLossPoints = 200;
input int TakeProfitPoints = 400;
input int BreakoutBufferPoints = 10;These inputs can be changed without editing the code.
This is useful when testing different versions of the same idea.
A small monitoring EA
Before placing trades, it helps to build an EA that only observes the chart.
This version displays the current symbol, timeframe, bid, ask, spread, and previous candle levels.
#property strict
input bool ShowSpread = true;
void OnTick()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double previousHigh = iHigh(_Symbol, _Period, 1);
double previousLow = iLow(_Symbol, _Period, 1);
double spreadPoints = (ask - bid) / _Point;
string text = "";
text += "Symbol: " + _Symbol + "\n";
text += "Timeframe: " + EnumToString(_Period) + "\n";
text += "Bid: " + DoubleToString(bid, _Digits) + "\n";
text += "Ask: " + DoubleToString(ask, _Digits) + "\n";
text += "Previous high: " + DoubleToString(previousHigh, _Digits) + "\n";
text += "Previous low: " + DoubleToString(previousLow, _Digits) + "\n";
if(ShowSpread)
{
text += "Spread: " + DoubleToString(spreadPoints, 1) + " points\n";
}
Comment(text);
}This EA does not open trades.
It is only there to check that the program is reading the chart correctly.
That step is worth doing. A trading robot should not be trusted until its internal state is visible and understandable.
Quant Atlas’ indicators are now on Metatrader5!
Go to the MQL marketplace to find our products!
Opening trades with MQL5
MQL5 includes a trade class called CTrade.
It provides simple methods for opening and managing trades.
To use it, include the trade library and create a trade object.
#include <Trade/Trade.mqh>
CTrade trade;A basic buy order can be sent like this.
trade.Buy(0.10, _Symbol);A basic sell order can be sent like this.
trade.Sell(0.10, _Symbol);Those examples are intentionally incomplete.
An EA should not send trades without conditions. It also needs to check whether a position already exists.
This example opens one buy position only if there is no current position on the symbol.
#include <Trade/Trade.mqh>
CTrade trade;
void OnTick()
{
if(PositionSelect(_Symbol))
return;
trade.Buy(0.10, _Symbol);
}This is still not a strategy. It is only an execution example.
The EA now needs a rule.
Simple strategy: previous candle breakout
The first strategy will use the previous candle.
The rule is simple.
If the ask price moves above the previous candle high, the EA opens a buy trade.
If the bid price moves below the previous candle low, the EA opens a sell trade.
The EA uses a small buffer so it does not enter exactly at the high or low.
It also uses a fixed stop loss and take profit.
It allows only one open position per symbol.
It allows only one trade per candle.
This is not presented as a profitable system.
It is a clean first example because the rules are specific and easy to inspect.
Complete Expert Advisor
#property strict
#include <Trade/Trade.mqh>
CTrade trade;
input double Lots = 0.10;
input int StopLossPoints = 200;
input int TakeProfitPoints = 400;
input int BreakoutBufferPoints = 10;
input int MagicNumber = 10001;
datetime lastBarTime = 0;
double previousHigh = 0.0;
double previousLow = 0.0;
bool tradedThisBar = false;
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
Print("Previous Candle Breakout EA started.");
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
Comment("");
Print("Previous Candle Breakout EA stopped.");
}
void OnTick()
{
UpdateCandleData();
if(previousHigh <= 0.0 || previousLow <= 0.0)
return;
if(tradedThisBar)
return;
if(PositionSelect(_Symbol))
return;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double buffer = BreakoutBufferPoints * _Point;
double buyLevel = previousHigh + buffer;
double sellLevel = previousLow - buffer;
ShowStatus(bid, ask, buyLevel, sellLevel);
if(ask > buyLevel)
{
OpenBuy(ask);
tradedThisBar = true;
}
if(bid < sellLevel)
{
OpenSell(bid);
tradedThisBar = true;
}
}
void UpdateCandleData()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == lastBarTime)
return;
lastBarTime = currentBarTime;
previousHigh = iHigh(_Symbol, _Period, 1);
previousLow = iLow(_Symbol, _Period, 1);
tradedThisBar = false;
Print(
"New candle. Previous high: ",
DoubleToString(previousHigh, _Digits),
" Previous low: ",
DoubleToString(previousLow, _Digits)
);
}
void OpenBuy(double ask)
{
double stopLoss = NormalizeDouble(ask - StopLossPoints * _Point, _Digits);
double takeProfit = NormalizeDouble(ask + TakeProfitPoints * _Point, _Digits);
bool result = trade.Buy(
Lots,
_Symbol,
ask,
stopLoss,
takeProfit,
"Previous candle high breakout"
);
if(result)
{
Print("Buy trade opened.");
}
else
{
Print(
"Buy trade failed. Retcode: ",
trade.ResultRetcode(),
" ",
trade.ResultRetcodeDescription()
);
}
}
void OpenSell(double bid)
{
double stopLoss = NormalizeDouble(bid + StopLossPoints * _Point, _Digits);
double takeProfit = NormalizeDouble(bid - TakeProfitPoints * _Point, _Digits);
bool result = trade.Sell(
Lots,
_Symbol,
bid,
stopLoss,
takeProfit,
"Previous candle low breakout"
);
if(result)
{
Print("Sell trade opened.");
}
else
{
Print(
"Sell trade failed. Retcode: ",
trade.ResultRetcode(),
" ",
trade.ResultRetcodeDescription()
);
}
}
void ShowStatus(double bid, double ask, double buyLevel, double sellLevel)
{
Comment(
"Previous Candle Breakout EA\n",
"Symbol: ", _Symbol, "\n",
"Timeframe: ", EnumToString(_Period), "\n",
"Bid: ", DoubleToString(bid, _Digits), "\n",
"Ask: ", DoubleToString(ask, _Digits), "\n",
"Previous high: ", DoubleToString(previousHigh, _Digits), "\n",
"Previous low: ", DoubleToString(previousLow, _Digits), "\n",
"Buy level: ", DoubleToString(buyLevel, _Digits), "\n",
"Sell level: ", DoubleToString(sellLevel, _Digits), "\n",
"Traded this bar: ", tradedThisBar ? "yes" : "no"
);
}How the EA works
The EA starts by loading the trade library.
#include <Trade/Trade.mqh>Then it creates a trade object.
CTrade trade;The trade object is used later to open buy and sell positions.
The inputs define the values the user can change from the EA settings window.
input double Lots = 0.10;
input int StopLossPoints = 200;
input int TakeProfitPoints = 400;
input int BreakoutBufferPoints = 10;
input int MagicNumber = 10001;OnInit() runs once when the EA starts.
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
Print("Previous Candle Breakout EA started.");
return INIT_SUCCEEDED;
}The magic number helps identify trades created by this EA.
OnTick() runs when a new price update arrives.
The first thing it does is update candle data.
UpdateCandleData();Then it checks whether the previous candle values are valid.
if(previousHigh <= 0.0 || previousLow <= 0.0)
return;Then it checks whether the EA has already traded during the current candle.
if(tradedThisBar)
return;Then it checks whether there is already an open position on the symbol.
if(PositionSelect(_Symbol))
return;After those checks, the EA reads the current bid and ask.
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);Then it calculates the breakout levels.
double buffer = BreakoutBufferPoints * _Point;
double buyLevel = previousHigh + buffer;
double sellLevel = previousLow - buffer;If the ask price rises above the buy level, the EA opens a buy trade.
if(ask > buyLevel)
{
OpenBuy(ask);
tradedThisBar = true;
}If the bid price falls below the sell level, the EA opens a sell trade.
if(bid < sellLevel)
{
OpenSell(bid);
tradedThisBar = true;
}The trade functions calculate stop loss and take profit before sending the order.
double stopLoss = NormalizeDouble(ask - StopLossPoints * _Point, _Digits);
double takeProfit = NormalizeDouble(ask + TakeProfitPoints * _Point, _Digits);NormalizeDouble() adjusts the price to the correct number of decimal places for the symbol.
The EA also prints errors when trade execution fails.
Print(
"Buy trade failed. Retcode: ",
trade.ResultRetcode(),
" ",
trade.ResultRetcodeDescription()
);That part matters. If an order fails, the reason should be visible in the log.
How to test it
Run this EA in the Strategy Tester before using it anywhere else.
Use a demo account while learning.
Start with one symbol and one timeframe.
Keep the first test simple.
Watch whether the EA opens trades where expected.
Check the log.
Check whether the stop loss and take profit are placed correctly.
Check whether it avoids repeated trades during the same candle.
The first goal is not performance.
The first goal is correctness.
Does the EA do what the code says? That question comes before any discussion about profit.
What this first EA teaches
This EA introduces the basic structure used by many trading robots.
It reads live market data.
It reads completed candle data.
It waits for a specific condition.
It checks whether a position already exists.
It sends an order.
It places stop loss and take profit levels.
It displays its state on the chart.
It writes useful messages to the log.
The strategy itself is deliberately simple.
That makes it easier to inspect.
A complicated EA with unclear behavior is hard to fix. A simple EA with visible logic can be tested, changed, and improved.
🚨🚨Get your Quant Atlas Free trial (no credit card required).
Sign up takes ~9 seconds before you have unconditional 10-day access to 100+ markets.





