A C++ compiler and a terminal. No MetaTrader, no broker, no account and no
money. Most Linux machines already have g++; on macOS run
xcode-select --install once; on Windows use the Windows Subsystem for Linux or MSYS2.
Check with g++ --version.
What this page does and does not give you. It runs the MQL5 language — types, operators, control flow, functions, arrays — on your own machine, so you can learn and experiment without installing a trading platform. It is not MetaTrader: there are no charts, no live prices, no orders, and nothing here can trade. Real Expert Advisors are still compiled in MetaEditor and still have to be tested on a demo account first.
Nothing here is trading advice, and none of this code trades. Run the five files in order; the first two are the toolchain and the rest build on them.
What is MQL5?
MQL5 (MetaQuotes Language 5) is a high-level, C++-like programming language designed specifically for developing trading applications on the MetaTrader 5 platform. With MQL5, you can create custom indicators, Expert Advisors (automated trading systems), scripts, and libraries.
MQL5 is a significant upgrade from MQL4. It supports object-oriented programming, has a richer standard library, faster execution speed, and access to more market data. If you know C, C++, or Java, MQL5 syntax will feel familiar.
Opening MetaEditor
MetaEditor is the integrated development environment (IDE) that comes with MetaTrader 5. To open it:
- From MT5: Press F4 or click Tools > MetaQuotes Language Editor
- From Windows: Find MetaEditor in your Start Menu under the MetaTrader 5 folder
MetaEditor provides syntax highlighting, auto-completion, a built-in compiler, a debugger, and direct access to the MQL5 Reference documentation.
MQL5 Program Types
There are four types of MQL5 programs, each serving a different purpose:
- Scripts (.mq5) — Run once and exit. Use for one-time tasks like closing all orders, exporting data, or sending a notification.
- Indicators (.mq5) — Run continuously on a chart, processing every new tick or bar. They calculate and display visual information (lines, arrows, histograms) on the chart.
- Expert Advisors (.mq5) — Run continuously and can place, modify, and close trades automatically. They implement your trading strategy in code.
- Libraries (.mq5) — Reusable code modules that can be imported by other programs.
Your First MQL5 Script
Let us write a simple script that prints a message to the Experts log:
In MetaEditor, go to File > New (or press Ctrl + N). Select "Script" and click Next. Name it "HelloWorld" and click Finish.
Replace the generated code with:
//+------------------------------------------------------------------+
//| HelloWorld.mq5 |
//| A simple script that prints to the Experts log |
//+------------------------------------------------------------------+
void OnStart()
{
Print("Hello, MQL5 World!");
Print("Account balance: ", AccountInfoDouble(ACCOUNT_BALANCE));
Print("Current symbol: ", _Symbol);
Print("Current timeframe: ", EnumToString(_Period));
Alert("Script executed successfully!");
}
Press F7 or click Compile. If there are no errors, you will see "0 errors, 0 warnings" in the output panel.
Switch back to MT5 (F4). In the Navigator panel, expand Scripts, find HelloWorld, and drag it onto any chart. Check the Experts tab in the Toolbox to see the output.
Understanding the File Structure
MQL5 programs are stored in the MT5 data folder:
MQL5/
Experts/ <-- Expert Advisors (.mq5 and .ex5)
Indicators/ <-- Custom Indicators
Scripts/ <-- Scripts
Include/ <-- Header files (.mqh)
Libraries/ <-- Library files
Source files have the .mq5 extension. When you compile, MetaEditor creates an .ex5 file (executable) in the same directory. MT5 runs the .ex5 files — the .mq5 source is only needed for development.
The Compilation Process
MQL5 is a compiled language, not interpreted. This means:
- You write source code in
.mq5files - MetaEditor compiles it into optimized
.ex5bytecode - MT5 executes the bytecode at near-native speed
- If you share an
.ex5file without the.mq5source, others can run your program but cannot see or modify your code
When selling or distributing indicators and EAs, you only need to share the compiled .ex5 file. Your source code (.mq5) remains private. This is how the MQL5 Market works — buyers get executables, not source code.
Key MQL5 Resources
- MQL5 Reference — Press F1 in MetaEditor or access it from Help > MQL5 Reference. This is the complete language documentation.
- MQL5.com Community — Forum, articles, Code Base (free code), and the Market (paid indicators/EAs).
- MetaEditor's Code Completion — Type the first few letters of any function and press Ctrl + Space for auto-completion suggestions.
Run Your First MQL5 Code Without Installing MetaTrader, in Five Steps
The usual first obstacle with MQL5 is not the language — it is that every tutorial begins “open MetaEditor”, which means installing a Windows trading platform before you can run a single line. You can skip that for the learning part. MQL5 is deliberately close to C++, so a small header file supplying the handful of built-ins MetaTrader provides is enough to compile and run real MQL5 on any machine with a C++ compiler. In the next half hour you will set that up, run your first program, and meet the three mistakes that catch every newcomer — including the one that makes an Expert Advisor silently do nothing at all. Every line of output below came from running these files.
Go: open a terminal in a folder you can write to — cd ~/Desktop on macOS or Linux, cd %USERPROFILE%\Desktop on Windows.
Do: save this as mql5.h. It supplies the small set of things MetaTrader would
provide — Print, ArraySize, DoubleToString, the
string and datetime types — so that MQL5 source compiles with an
ordinary C++ compiler.
// mql5.h - a small shim that lets a useful SUBSET of MQL5 compile and run
// with an ordinary C++ compiler, so you can practise the language without
// MetaTrader. It is NOT MetaTrader and does not trade anything.
#pragma once
#include <cstdio>
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
typedef std::string string;
typedef long long datetime;
#define input static
template <typename T> int ArraySize(const std::vector<T> &a) { return (int)a.size(); }
template <typename T> void ArrayResize(std::vector<T> &a, int n) { a.resize(n); }
inline void Print() { std::cout << std::endl; }
template <typename T, typename... Rest>
void Print(const T &first, const Rest &...rest) {
std::cout << first;
if constexpr (sizeof...(rest) > 0) Print(rest...);
else std::cout << std::endl;
}
inline string DoubleToString(double v, int digits = 8) {
char buf[64]; snprintf(buf, sizeof buf, "%.*f", digits, v); return string(buf);
}
inline string IntegerToString(long long v) { return std::to_string(v); }
inline double MathAbs(double v) { return std::fabs(v); }
inline double MathMax(double a, double b) { return a > b ? a : b; }
inline double MathMin(double a, double b) { return a < b ? a : b; }
inline int MathRound(double v) { return (int)std::lround(v); }
You should see: nothing — this is a header file, not a program. Confirm it saved with
wc -l mql5.h (Windows PowerShell: (Get-Content mql5.h).Count), which
should report a line count in the low thirties.
Be clear about what this is and is not. It runs the language — types, operators, control flow, functions, arrays — which is the part you actually have to learn. It is not MetaTrader: there are no charts, no prices, no orders, and nothing here can place a trade. When you come to write a real Expert Advisor you will still compile it in MetaEditor, and it will still need testing on a demo account.
What you gain is the ability to try a language idea in five seconds instead of switching to a platform, and to do it on a Mac or a Linux machine.
If you do not have a compiler: on Ubuntu or Debian,
sudo apt install g++; on macOS, xcode-select --install; on Windows,
install MSYS2 or use the Windows Subsystem for Linux, or simply skip to using MetaEditor, which
you will have anyway if MetaTrader 5 is installed.
If not: Permission denied when running the script means the
chmod +x step was missed — or just run it as sh build.sh NAME,
which needs no execute bit. If neither compiler is found the script says so and stops rather than
failing obscurely.
Go: the same folder.
Do: save this as build.sh. It compiles a .mq5 file and runs it,
using whichever C++ compiler you have. Then make it runnable with
chmod +x build.sh.
#!/bin/sh
# build.sh NAME -> compiles NAME.mq5 and runs it.
# MQL5 is close enough to C++ that a C++ compiler can run the subset used here,
# with mql5.h supplying the handful of built-ins MetaTrader would provide.
# Uses whichever compiler you have.
set -e
NAME="$1"
if command -v g++ >/dev/null 2>&1 && g++ -x c++ -std=c++17 -o "$NAME" "$NAME.mq5" 2>/dev/null; then
:
elif command -v clang++ >/dev/null 2>&1; then
clang++ -x c++ -std=c++17 -o "$NAME" "$NAME.mq5"
else
echo "No C++ compiler found. Install g++ (Linux: apt install g++, macOS: xcode-select --install)." >&2
exit 1
fi
"./$NAME"
You should see: nothing yet — this is the toolchain. Check you have a compiler by running
g++ --version, which should print a line naming a version, like this one:
g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
The script tries g++ first and falls back to clang++, so
the same command works on Linux, macOS and inside the Windows Subsystem for Linux. If you would
rather not use a script, the command it runs is
g++ -x c++ -std=c++17 -o NAME NAME.mq5 followed by ./NAME — the
-x c++ is what tells the compiler to treat a .mq5 file as C++ source.
If you have no compiler: on Ubuntu or Debian,
sudo apt install g++; on macOS, xcode-select --install; on Windows,
install MSYS2 or use the Windows Subsystem for Linux — or skip to MetaEditor, which you
already have if MetaTrader 5 is installed.
If not: Permission denied means the chmod +x step was missed; you
can also just run it as sh build.sh NAME, which needs no execute bit. If neither
compiler is found the script says so and stops rather than failing obscurely.
Go: the same folder.
Do: save this as hello.mq5 and run sh build.sh hello. Note the
file keeps the real .mq5 extension, so the same file opens in MetaEditor.
#include "mql5.h"
//+------------------------------------------------------------------+
//| In MetaTrader a script starts at OnStart(). Outside it, at main().|
//| The shim maps one to the other so the same file works in both. |
//+------------------------------------------------------------------+
void OnStart()
{
Print("MQL5 is running.");
double lot = 0.10;
int digits = 5;
string symbol = "EURUSD";
datetime when = 1700000000;
bool is_open = true;
Print("symbol : ", symbol);
Print("lot : ", DoubleToString(lot, 2));
Print("digits : ", digits);
Print("time : ", when);
Print("market : ", is_open ? "open" : "closed");
}
int main() { OnStart(); return 0; }
You should see: six lines, one per variable:
MQL5 is running.
symbol : EURUSD
lot : 0.10
digits : 5
time : 1700000000
market : open
Everything in that file is genuine MQL5 except the final main() line,
which exists only so a desktop compiler knows where to start. In MetaTrader, a script's entry point
is OnStart(); an Expert Advisor uses OnTick(); an indicator uses
OnCalculate(). Delete the main line and the rest is a valid MQL5
script.
Two types are worth noticing now. datetime is a whole number of seconds since 1970
— that is why it printed as a large integer rather than a date. And string is
built into the language rather than being a class you include.
If not: fatal error: mql5.h: No such file or directory means the header is not
in the same folder as the .mq5 file — run ls (Windows:
dir) and confirm both names appear. A long list of errors mentioning
std:: usually means the -std=c++17 flag was lost from the build
script.
Go: the same folder.
Do: save this as types.mq5 and run sh build.sh types.
#include "mql5.h"
//+------------------------------------------------------------------+
//| The trap that catches everyone once: integer division. |
//+------------------------------------------------------------------+
void OnStart()
{
int a = 7, b = 2;
double c = 7.0, d = 2.0;
Print("int 7 / int 2 = ", a / b, " <- the .5 is DISCARDED");
Print("double 7.0 / 2.0 = ", c / d);
Print("int 7 / double 2.0 = ", a / d, " <- one double is enough");
Print("(double)7 / 2 = ", (double)a / b);
Print("");
// Where this actually bites: position sizing.
int balance = 1000;
int risk_percent = 2;
Print("risk 2% as int : ", DoubleToString(balance * risk_percent / 100, 2));
Print("risk 0.5% as double: ", DoubleToString(balance * 0.5 / 100, 2), " <- correct");
int half_percent_as_int = 1 / 2; // 0.5 written with ints
Print("risk 0.5% as int : ", DoubleToString(balance * half_percent_as_int / 100.0, 2),
" <- 1/2 became 0 BEFORE the multiply");
Print("");
Print("A lot size computed with integer division silently becomes 0,");
Print("and an order with lot 0 is rejected -- so the symptom is an EA");
Print("that 'does nothing' with no error you can see.");
}
int main() { OnStart(); return 0; }
You should see: seven divided by two coming out as three:
int 7 / int 2 = 3 <- the .5 is DISCARDED
double 7.0 / 2.0 = 3.5
int 7 / double 2.0 = 3.5 <- one double is enough
(double)7 / 2 = 3.5
risk 2% as int : 20.00
risk 0.5% as double: 5.00 <- correct
risk 0.5% as int : 0.00 <- 1/2 became 0 BEFORE the multiply
A lot size computed with integer division silently becomes 0,
and an order with lot 0 is rejected -- so the symptom is an EA
that 'does nothing' with no error you can see.
When both operands are whole numbers, the division is a whole-number division and the remainder is discarded. There is no warning, because nothing is wrong — it is what the language was asked to do.
The reason this matters more in trading code than elsewhere is the last block.
A lot size computed with integer arithmetic quietly becomes 0.00, and an order for
zero lots is rejected by the server. The symptom is an Expert Advisor that runs, logs nothing
unusual, and never opens a position — which people spend days debugging as a strategy problem
when it is an arithmetic one.
The habit that prevents it: write price and volume literals with a decimal point
— 100.0 rather than 100 — so that any expression touching them
is done in doubles.
If not: if int 7 / int 2 prints 3.5, the variables were declared as
double — they must be int for the effect to appear, which is
itself the whole point.
Go: the same folder.
Do: save this as series.mq5 and run sh build.sh series.
#include "mql5.h"
//+------------------------------------------------------------------+
//| MQL5's most distinctive trap: which end of the array is "now"? |
//+------------------------------------------------------------------+
void OnStart()
{
// Five closes, oldest first -- the order a file or a loop produces.
std::vector<double> chronological = {10.0, 11.0, 12.0, 13.0, 14.0};
Print("stored oldest-first:");
for(int i = 0; i < ArraySize(chronological); i++)
Print(" index ", i, " = ", DoubleToString(chronological[i], 1));
Print("");
Print("index 0 is the OLDEST bar: ", DoubleToString(chronological[0], 1));
// ArraySetAsSeries(arr, true) reverses what the indexes mean.
std::vector<double> as_series(chronological.rbegin(), chronological.rend());
Print("");
Print("after ArraySetAsSeries(arr, true):");
for(int i = 0; i < ArraySize(as_series); i++)
Print(" index ", i, " = ", DoubleToString(as_series[i], 1));
Print("");
Print("index 0 is now the NEWEST bar: ", DoubleToString(as_series[0], 1));
Print("");
Print("Same array. Same memory. The indexes mean the opposite thing.");
Print("Price arrays from CopyClose() and friends are NOT series-ordered");
Print("by default -- you set that yourself, and forgetting is why an");
Print("indicator reads the oldest bar every tick and never updates.");
}
int main() { OnStart(); return 0; }
You should see: the same five numbers indexed from both ends:
stored oldest-first:
index 0 = 10.0
index 1 = 11.0
index 2 = 12.0
index 3 = 13.0
index 4 = 14.0
index 0 is the OLDEST bar: 10.0
after ArraySetAsSeries(arr, true):
index 0 = 14.0
index 1 = 13.0
index 2 = 12.0
index 3 = 11.0
index 4 = 10.0
index 0 is now the NEWEST bar: 14.0
Same array. Same memory. The indexes mean the opposite thing.
Price arrays from CopyClose() and friends are NOT series-ordered
by default -- you set that yourself, and forgetting is why an
indicator reads the oldest bar every tick and never updates.
This is the single most distinctive thing about MQL5 arrays and the one that causes
the most confusion. ArraySetAsSeries(arr, true) makes index 0 the most
recent bar, counting backwards into history — which is how a chart is naturally read, and
how most MQL5 examples are written.
But arrays filled by CopyClose, CopyRates and friends are not
series-ordered unless you say so. Forget the call and index 0 is the oldest bar in the
buffer, so your indicator faithfully recalculates the same ancient bar on every tick and appears
frozen. Nothing errors; the numbers are simply from the wrong end of history.
The rule is short: set the direction explicitly on every array you receive price data into, immediately after you receive it, and never rely on the default.
If not: if both listings are identical, the reversed copy was built from the wrong iterators
— it needs rbegin() and rend(). In real MQL5 you would call
ArraySetAsSeries(arr, true) and the array itself would change meaning; the shim shows
the effect by reversing a copy, which is easier to see.
Go: the same folder.
Do: save this as errors.mq5 and run sh build.sh errors.
#include "mql5.h"
//+------------------------------------------------------------------+
//| Four things that compile and are still wrong. |
//+------------------------------------------------------------------+
void OnStart()
{
// 1. Comparing doubles for equality.
double a = 0.1 + 0.2;
double b = 0.3;
Print("0.1 + 0.2 == 0.3 ? ", (a == b) ? "true" : "FALSE");
Print(" a is actually : ", DoubleToString(a, 20));
Print(" compare with a tolerance instead:");
Print(" MathAbs(a-b) < 1e-9 ? ", (MathAbs(a - b) < 1e-9) ? "true" : "false");
Print("");
// 2. An index one past the end.
std::vector<double> price = {1.0, 2.0, 3.0};
Print("ArraySize(price) : ", ArraySize(price));
Print("last valid index : ", ArraySize(price) - 1);
Print(" a loop to <= size reads one element past the end.");
Print("");
// 3. Assignment inside a condition.
int bars = 0;
if((bars = 5) > 0) // '=' not '=='
Print("assignment in a condition: bars is now ", bars, " and the test passed");
Print("");
// 4. Losing precision by declaring a price as int.
int wrong = (int)1.10250;
double right = 1.10250;
Print("price stored as int : ", wrong, " <- the entire quote is gone");
Print("price stored as double : ", DoubleToString(right, 5));
}
int main() { OnStart(); return 0; }
You should see: 0.1 plus 0.2 not equalling 0.3:
0.1 + 0.2 == 0.3 ? FALSE
a is actually : 0.30000000000000004441
compare with a tolerance instead:
MathAbs(a-b) < 1e-9 ? true
ArraySize(price) : 3
last valid index : 2
a loop to <= size reads one element past the end.
assignment in a condition: bars is now 5 and the test passed
price stored as int : 1 <- the entire quote is gone
price stored as double : 1.10250
The first is not an MQL5 quirk — it is how binary floating point works
everywhere, and it matters here because prices are doubles. Never compare two prices with
==. Compare the difference against a tolerance, or against the instrument's
own tick size, which MQL5 exposes as SYMBOL_TRADE_TICK_SIZE.
The third is worth reading twice. if((bars = 5) > 0) uses a single
=, so it assigns rather than compares — and the condition is then always true,
because 5 is greater than 0. It compiles, it runs, and the bug is invisible in a code review that
is going quickly.
The fourth is the one that appears in real trading code: a price declared as int
loses everything after the decimal point, so 1.10250 becomes 1. If your
stop-loss calculations come out as whole numbers, this is why.
If not: if the first line prints true, your compiler has evaluated the constant
expression at compile time with higher precision — assign the operands to variables first, as
the file does, to force it to happen at run time.
Without scrolling up: an Expert Advisor compiles cleanly, runs on a chart, prints its
startup message, and never opens a single trade. No errors appear in the journal. Name two things
from this page that would produce exactly that, and say how you would tell them apart. Answer: the first is integer division in the position-sizing calculation: step 3 showed a lot size
computed from whole numbers collapsing to 0.00, and an order for zero lots is refused
by the server without anything looking broken. The second is an array direction problem: step 4
showed that a price array which was never set as a series has index 0 at the oldest bar,
so a condition written as “compare the current close with the previous one” is
comparing two bars from the distant past that never change, and the entry condition is simply never
met. To tell them apart, print the values rather than reasoning about them — log the computed
lot size and the first two elements of the price array on each tick. If the lot prints as 0, it is
the first; if the prices are correct-looking numbers that never change from tick to tick, it is the
second. Printing the intermediate values is faster than reading the code, and it is what the
journal is for.
Now do it without the page: extend types.mq5 with a realistic position-size function — take a
balance, a risk percentage and a stop distance in points, and return a lot size — then
deliberately declare one of its parameters as int and watch what the function returns.
Fix it, then write down the rule you will follow from now on about which of your variables are
allowed to be whole numbers. For most trading code the honest answer is: bar counts and array
indexes, and nothing else.
Next Steps
Now that you can create and run MQL5 programs, the next tutorial covers variables, data types, and operators — the building blocks of every MQL5 program.