Skip to content

Built-in Indicators Overview

Tour the standard indicators that ship with MT5 and learn when to use each one.

💡
Before you start

Python 3 and a terminal for most of it; a C++ compiler for step 2 only. No MetaTrader, no broker, no account and no money. macOS and Linux already include Python; on Windows install it from python.org with “Add python.exe to PATH” ticked.

Steps 3 and 4 are the ones that change how people use indicators, and they need only Python — so if you have no compiler, skip step 2 and lose nothing important.

Nothing here is trading advice, and the agreement figures in the last step describe one generated series rather than any real market. The method transfers; the exact percentages do not.

Indicator Categories in MT5

MT5 ships with 38 built-in technical indicators organized into four categories. Understanding which category an indicator belongs to helps you know what question it answers about the market.

  • Trend Indicators — Answer: "What direction is the market moving?" (e.g., Moving Averages, Bollinger Bands, Ichimoku)
  • Oscillators — Answer: "Is the market overbought or oversold?" (e.g., RSI, MACD, Stochastic)
  • Volume Indicators — Answer: "How much participation is behind this move?" (e.g., OBV, Volumes, Money Flow Index)
  • Bill Williams Indicators — A specialized set based on Bill Williams' trading theory (e.g., Alligator, Fractals, Awesome Oscillator)

Essential Trend Indicators

Moving Average (MA) — The most fundamental indicator. It smooths price data into a flowing line that shows the trend direction. MT5 supports Simple (SMA), Exponential (EMA), Smoothed (SMMA), and Linear Weighted (LWMA) types. Common settings: 20-period for short-term, 50-period for medium-term, 200-period for long-term trend.

Bollinger Bands — Three lines: a middle SMA with upper and lower bands at 2 standard deviations. When bands widen, volatility is increasing. When bands narrow (a "squeeze"), a breakout is likely coming. Price touching the upper band suggests overbought; touching the lower band suggests oversold.

Ichimoku Kinko Hyo — A comprehensive system showing support/resistance, trend direction, and momentum all at once. Consists of five lines: Tenkan-sen, Kijun-sen, Senkou Span A, Senkou Span B, and Chikou Span. The "cloud" between Senkou Span A and B is a key feature.

Essential Oscillators

RSI (Relative Strength Index) — Measures momentum on a 0-100 scale. Above 70 = overbought, below 30 = oversold. Divergences between RSI and price are powerful reversal signals. Default period: 14.

MACD (Moving Average Convergence Divergence) — Shows the relationship between two EMAs. Consists of the MACD line, signal line, and histogram. Crossovers of the MACD and signal lines generate buy/sell signals. Histogram bars show momentum strength.

Stochastic Oscillator — Compares a closing price to its price range over a period. Generates %K and %D lines on a 0-100 scale. Like RSI, above 80 is overbought and below 20 is oversold, but Stochastic is more sensitive to price changes.

Volume Indicators

Volumes — Shows the number of ticks (price changes) per candle. In forex, tick volume is a proxy for real volume and correlates strongly with actual market activity.

On Balance Volume (OBV) — Running total that adds volume on up-candles and subtracts on down-candles. Rising OBV confirms an uptrend; divergence between OBV and price warns of a potential reversal.

Money Flow Index (MFI) — Like RSI but incorporates volume, making it a volume-weighted momentum oscillator. Often called "volume RSI." Above 80 is overbought, below 20 is oversold.

Combining Indicators Effectively

⚠️
Avoid indicator overload

Using too many indicators creates conflicting signals and analysis paralysis. A good rule: use one trend indicator, one oscillator, and optionally one volume indicator. More than 3-4 indicators on a chart is usually counterproductive.

Effective indicator combinations use indicators from different categories that confirm each other:

  • Trend + Oscillator: EMA (200) for trend direction + RSI (14) for entry timing
  • Trend + Volume: Bollinger Bands for volatility + OBV for confirmation
  • Multi-MA: 50 EMA + 200 EMA for golden/death cross signals

Using two oscillators together (e.g., RSI + Stochastic) adds little value because they measure similar things.

Find Out What the Built-In Indicators Really Compute, in Four Steps

MetaTrader ships with dozens of indicators, which invites a natural but expensive mistake: adding several to a chart and treating their agreement as evidence. In the next half hour you will compute four of the most popular ones from scratch in a few lines each, then measure how much they agree — and find one pair that agrees on 100% of bars because they are the same arithmetic under two names. You will also meet the biggest difference between MetaTrader 4 and 5, which catches almost everyone once. Every line of output below came from running these files.

1
Save the two setup files, if you have not already

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. Only step 2 needs it; steps 3 and 4 are plain Python. If you already have it from an earlier page, reuse that copy.

// 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. Confirm with ls mql5.h (Windows: dir mql5.h). You will also need build.sh from any earlier MQL5 page for step 2.

If you would rather skip the compiler entirely, steps 3 and 4 are the ones that change how people use indicators, and they need only Python.

If not: no compiler? On Ubuntu or Debian sudo apt install g++; on macOS xcode-select --install; on Windows use the Windows Subsystem for Linux or MSYS2.

2
Meet the handle, and the mistake it invites

Go: the same folder, with build.sh present.

Do: save this as handles.mq5 and run sh build.sh handles.

#include "mql5.h"

//+------------------------------------------------------------------+
//| MT5's biggest change from MT4: you get a HANDLE, not a value.     |
//+------------------------------------------------------------------+

const int INVALID_HANDLE = -1;
int next_handle = 100;
std::vector<std::vector<double>> store;

// Stands in for iMA(): creates the indicator and returns a handle.
int iMA_stub(string symbol, int period)
{
    if(period < 1) return INVALID_HANDLE;
    std::vector<double> buf;
    for(int i = 0; i < 50; i++) buf.push_back(100.0 + i * 0.1);
    store.push_back(buf);
    return next_handle++;
}

// Stands in for CopyBuffer(): reads values OUT of an existing indicator.
int CopyBuffer_stub(int handle, int start, int count, std::vector<double> &dest)
{
    int idx = handle - 100;
    if(idx < 0 || idx >= (int)store.size()) return -1;
    ArrayResize(dest, count);
    for(int i = 0; i < count; i++) dest[i] = store[idx][start + i];
    return count;
}

int g_ma_handle = INVALID_HANDLE;

int OnInit()
{
    g_ma_handle = iMA_stub("EURUSD", 20);       // create ONCE
    if(g_ma_handle == INVALID_HANDLE)
    { Print("OnInit: iMA failed -> refuse to start"); return 1; }
    Print("OnInit: handle ", g_ma_handle, " created");
    return 0;
}

void OnTick(int tick)
{
    std::vector<double> ma;
    int got = CopyBuffer_stub(g_ma_handle, 0, 3, ma);   // read EVERY tick
    if(got <= 0) { Print("   tick ", tick, "  CopyBuffer failed"); return; }
    Print("   tick ", tick, "  ma[0]=", DoubleToString(ma[0], 4),
          "  ma[1]=", DoubleToString(ma[1], 4));
}

void OnStart()
{
    if(OnInit() != 0) return;
    for(int t = 1; t <= 3; t++) OnTick(t);

    Print("");
    Print("Now the mistake: creating the handle inside OnTick.");
    int before = next_handle;
    for(int t = 1; t <= 200; t++) iMA_stub("EURUSD", 20);
    Print("   handles created in 200 ticks : ", next_handle - before);
    Print("   indicator instances in memory: ", (int)store.size());
    Print("");
    Print("Each call to iMA() creates another indicator instance and another");
    Print("copy of its buffers. Called from OnTick, that is one per price");
    Print("change -- and the terminal slows, then runs out of handles.");
    Print("");
    Print("The rule: create handles in OnInit(), read them with CopyBuffer()");
    Print("in OnTick(). One is setup, the other is a query.");
}

int main() { OnStart(); return 0; }

You should see: one handle used across three ticks, then two hundred created by accident:

OnInit: handle 100 created
   tick 1  ma[0]=100.0000  ma[1]=100.1000
   tick 2  ma[0]=100.0000  ma[1]=100.1000
   tick 3  ma[0]=100.0000  ma[1]=100.1000

Now the mistake: creating the handle inside OnTick.
   handles created in 200 ticks : 200
   indicator instances in memory: 201

Each call to iMA() creates another indicator instance and another
copy of its buffers. Called from OnTick, that is one per price
change -- and the terminal slows, then runs out of handles.

The rule: create handles in OnInit(), read them with CopyBuffer()
in OnTick(). One is setup, the other is a query.

This is the largest practical difference between MetaTrader 4 and 5, and it catches nearly everyone once. In MT4, iMA(...) returned a value and you called it wherever you needed one. In MT5 it returns a handle — a reference to an indicator the terminal now maintains — and you read values out of it separately with CopyBuffer.

Written the MT4 way, the call sits inside OnTick, and every price change creates another indicator instance with its own buffers over the whole chart. Two hundred ticks, two hundred instances. The terminal slows, memory grows, and eventually handle creation fails — with no error that points anywhere near the cause.

The rule is one line: create handles in OnInit(), read them with CopyBuffer() in OnTick(). Creation is setup; reading is a query. And check the handle against INVALID_HANDLE before using it, because a bad parameter fails there rather than later.

If not: fatal error: mql5.h means the header is not in this folder. If the second block reports 0 handles created, the loop bound was changed — the point is that the count tracks the tick count exactly.

3
Compute four popular indicators from scratch

Go: the same folder.

Do: save this as whatthey.py and run python3 whatthey.py.

"""What the popular built-ins actually compute, in one line of arithmetic each."""

closes = [100, 102, 101, 104, 103, 105, 107, 106, 108, 110,
          109, 111, 113, 112, 114, 116, 115, 117, 119, 118]
highs  = [c + 1.2 for c in closes]
lows   = [c - 1.1 for c in closes]

def sma(v, i, n):
    return sum(v[i-n+1:i+1]) / n if i >= n-1 else None

def stddev(v, i, n):
    if i < n - 1: return None
    m = sma(v, i, n)
    return (sum((x - m) ** 2 for x in v[i-n+1:i+1]) / n) ** 0.5

def atr(h, l, c, i, n):
    if i < n: return None
    trs = []
    for k in range(i - n + 1, i + 1):
        tr = max(h[k] - l[k], abs(h[k] - c[k-1]), abs(l[k] - c[k-1]))
        trs.append(tr)
    return sum(trs) / n

def stochastic(h, l, c, i, n):
    if i < n - 1: return None
    hh, ll = max(h[i-n+1:i+1]), min(l[i-n+1:i+1])
    return 100.0 * (c[i] - ll) / (hh - ll) if hh != ll else 50.0

i = 19
n = 10
m = sma(closes, i, n)
sd = stddev(closes, i, n)

print("at bar %d, period %d:" % (i, n))
print()
print("Moving Average      = mean of the last n closes")
print("                    = %.4f" % m)
print()
print("Bollinger Bands     = MA plus/minus k standard deviations")
print("   middle           = %.4f" % m)
print("   upper (k=2)      = %.4f" % (m + 2 * sd))
print("   lower (k=2)      = %.4f" % (m - 2 * sd))
print("   width            = %.4f   <- this is the only NEW information" % (4 * sd))
print()
print("ATR                 = mean true range, where true range accounts")
print("                      for gaps between bars")
print("                    = %.4f" % atr(highs, lows, closes, i, n))
print()
print("Stochastic %K        = where the close sits in the n-bar range")
print("                    = %.2f  (0 = at the low, 100 = at the high)"
      % stochastic(highs, lows, closes, i, n))
print()
print("Every one of these is a few lines of arithmetic over the same closes,")
print("highs and lows. None of them consults anything the others cannot see.")
print()
print("Which is the useful conclusion: stacking four of them is not four")
print("opinions. Bollinger Bands CONTAIN a moving average, so 'price above")
print("the MA and above the middle band' is one condition written twice.")

You should see: each one reduced to a line of arithmetic:

at bar 19, period 10:

Moving Average      = mean of the last n closes
                    = 114.4000

Bollinger Bands     = MA plus/minus k standard deviations
   middle           = 114.4000
   upper (k=2)      = 120.4795
   lower (k=2)      = 108.3205
   width            = 12.1589   <- this is the only NEW information

ATR                 = mean true range, where true range accounts
                      for gaps between bars
                    = 2.8400

Stochastic %K        = where the close sits in the n-bar range
                    = 82.11  (0 = at the low, 100 = at the high)

Every one of these is a few lines of arithmetic over the same closes,
highs and lows. None of them consults anything the others cannot see.

Which is the useful conclusion: stacking four of them is not four
opinions. Bollinger Bands CONTAIN a moving average, so 'price above
the MA and above the middle band' is one condition written twice.

Written out like this, the family resemblance is hard to miss. A moving average is a mean. Bollinger Bands are that mean plus and minus a multiple of the standard deviation — so the middle band is literally the moving average, and the only new information in the whole indicator is the width, which measures recent volatility. ATR measures the same thing a different way, adding gaps between bars. The Stochastic asks where the close sits within the recent range.

None of them consults anything the others cannot see. There is no additional data source, no volume, no order book — just the same closes, highs and lows arranged differently.

That is worth knowing before adding a fifth one to a chart, and it is the setup for the next step.

If not: the figures come from a fixed price list and will match this page. A ZeroDivisionError in the Stochastic means the high and low of the window were equal, which the code handles by returning 50.

4
Measure how much your 'confirming' indicators agree

Go: the same folder.

Do: save this as redundant.py and run python3 redundant.py.

"""Are your 'confirming' indicators independent? Measure the agreement."""

closes = []
p = 100.0
import random
random.seed(6)
for i in range(300):
    p += random.uniform(-1.0, 1.0) + (0.25 if (i // 40) % 2 == 0 else -0.20)
    closes.append(p)

def sma(v, i, n):
    return sum(v[i-n+1:i+1]) / n if i >= n-1 else None

def ema_series(v, n):
    k = 2.0 / (n + 1)
    out = [None] * (n - 1)
    out.append(sum(v[:n]) / n)
    for x in v[n:]:
        out.append(x * k + out[-1] * (1 - k))
    return out

fast_e, slow_e = ema_series(closes, 12), ema_series(closes, 26)
macd = [None if None in (a, b) else a - b for a, b in zip(fast_e, slow_e)]

def signals(name):
    out = []
    for i in range(60, len(closes)):
        if name == "price above MA(50)":
            out.append(closes[i] > sma(closes, i, 50))
        elif name == "MA(20) above MA(50)":
            out.append(sma(closes, i, 20) > sma(closes, i, 50))
        elif name == "MACD above zero":
            out.append(macd[i] > 0)
        elif name == "price above BB middle(20)":
            out.append(closes[i] > sma(closes, i, 20))
        elif name == "price above MA(20)":
            out.append(closes[i] > sma(closes, i, 20))
    return out

NAMES = ["price above MA(50)", "MA(20) above MA(50)",
         "MACD above zero", "price above BB middle(20)", "price above MA(20)"]
sets = {n: signals(n) for n in NAMES}

print("agreement between each pair, over %d bars:" % len(sets[NAMES[0]]))
print()
print("%-28s %-28s %s" % ("A", "B", "AGREE"))
print("-" * 68)
for a in range(len(NAMES)):
    for b in range(a + 1, len(NAMES)):
        x, y = sets[NAMES[a]], sets[NAMES[b]]
        agree = sum(1 for p, q in zip(x, y) if p == q) / len(x)
        print("%-28s %-28s %5.1f%%" % (NAMES[a], NAMES[b], 100 * agree))

print()
print("Two independent yes/no signals would agree about half the time.")
print("Every pair here agrees far more than that, because all five are")
print("functions of the same closing prices.")
print()
print("And look at the 100.0% row. 'Bollinger middle band' IS a 20-period")
print("moving average -- the same arithmetic under a different name -- so")
print("those two agree on every single bar, by definition, forever.")
print()
print("Anyone using both as separate confirmations has one condition")
print("written twice, and has doubled their confidence without changing")
print("their accuracy at all.")
print()
print("Confirmation only means something when the second source could")
print("plausibly disagree. Views of the same closes cannot.")

You should see: every pair agreeing far more than chance, and one pair agreeing perfectly:

agreement between each pair, over 240 bars:

A                            B                            AGREE
--------------------------------------------------------------------
price above MA(50)           MA(20) above MA(50)           79.6%
price above MA(50)           MACD above zero               96.7%
price above MA(50)           price above BB middle(20)     81.7%
price above MA(50)           price above MA(20)            81.7%
MA(20) above MA(50)          MACD above zero               82.1%
MA(20) above MA(50)          price above BB middle(20)     61.3%
MA(20) above MA(50)          price above MA(20)            61.3%
MACD above zero              price above BB middle(20)     79.2%
MACD above zero              price above MA(20)            79.2%
price above BB middle(20)    price above MA(20)           100.0%

Two independent yes/no signals would agree about half the time.
Every pair here agrees far more than that, because all five are
functions of the same closing prices.

And look at the 100.0% row. 'Bollinger middle band' IS a 20-period
moving average -- the same arithmetic under a different name -- so
those two agree on every single bar, by definition, forever.

Anyone using both as separate confirmations has one condition
written twice, and has doubled their confidence without changing
their accuracy at all.

Confirmation only means something when the second source could
plausibly disagree. Views of the same closes cannot.

Two genuinely independent yes/no signals would agree about half the time. Every pair here agrees between 61% and 97%, because all of them are functions of the same closing prices.

And the 100.0% row settles the argument. “Price above the Bollinger middle band” and “price above the 20-period moving average” are the same comparison written twice, so they agree on every bar, in every market, forever. Anybody treating them as two confirmations has doubled their confidence and changed their accuracy by exactly nothing — which is the worst possible combination, because confidence is what determines position size.

The useful test before adding an indicator: what does this compute from that my existing ones cannot see? If the honest answer is “the same closes, rearranged”, it is not confirmation. Things that genuinely can disagree include volume, volatility, a related instrument, a longer timeframe, and the calendar — and even those need checking rather than assuming.

If not: if the 100% row is missing, the two 20-period signals are computing different things — both must use sma(closes, i, 20), which is the identity being demonstrated. The other percentages depend on the seeded series and will match this page.

🎉
Check yourself before moving on

Without scrolling up: a strategy enters when price is above the 200 EMA, the MACD is above zero, the RSI is above 50, and price is above the Bollinger middle band — described as “four independent confirmations”. How would you test that claim, and what do you expect to find? Answer: test it the way step 5 did: compute all four as true/false series over a few hundred bars and measure how often each pair agrees. Two independent signals agree about half the time, so anything far above that is redundancy rather than confirmation. What I expect to find is high agreement throughout, because every one of the four is computed from the same closing prices — MACD above zero means one EMA is above another, price above the Bollinger middle band is price above a moving average, and RSI above 50 means recent gains have outweighed recent losses, which in a rising market is true whenever the moving averages are rising too. The measured version in step 5 found pairs agreeing 61% to 97%, with one pair at 100% because it was the same calculation named twice. The practical consequence is not that the strategy is wrong but that it has fewer conditions than it thinks, so it will fire less often than expected and each entry carries less independent evidence than the count suggests.

Now do it without the page: add RSI to redundant.py as a fifth signal — “RSI(14) above 50” — and see where it lands in the agreement table. Then add one thing that is genuinely different: a signal based on the ATR from step 4, such as “today's range is above its 20-day average”. Volatility can rise in a falling market and fall in a rising one, so its agreement with the trend signals should be much closer to 50%. That row is what confirmation actually looks like, and having seen it once you will recognise its absence.

Custom Indicators

Beyond the 38 built-in indicators, MT5 supports unlimited custom indicators written in MQL5. Custom indicators let you visualize any calculation or data combination you can imagine. You can find thousands of free custom indicators on the MQL5.com community, or you can build your own — which we cover in the MQL5 Programming section of this tutorial series.

💡
Need a custom indicator?

If you have a specific trading idea that no existing indicator covers, a custom MQL5 indicator can bring it to life. finkatana.com offers professional custom indicator development — visit our Services page to learn more.