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.
If you have done an earlier MQL5 page you already have the two setup files — reuse them and start at step 3.
Every bug on this page is SILENT. None of them produces an error message, a warning or a failed compile; they produce a slow terminal, values that drift, and a chart that looks squashed. That is exactly why they are worth measuring rather than reading about.
Understanding Buffers and Plots
In MQL5, indicator buffers and plots are separate concepts:
- Buffers are arrays that store calculated values. Some are drawn on the chart, others are used internally for calculations.
- Plots are the visual representations. Each plot uses one or more buffers depending on the drawing style.
The number of buffers is always >= the number of plots. For example, a candlestick plot requires 4 buffers (open, high, low, close) but is only 1 plot.
Drawing Styles Reference
MQL5 offers 18 drawing styles. Here are the most commonly used:
DRAW_LINE — A continuous line connecting values. Uses 1 buffer. The most common style for moving averages, channels, and signal lines.
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2
#property indicator_style1 STYLE_SOLID // SOLID, DASH, DOT, DASHDOT
DRAW_HISTOGRAM — Vertical bars from zero line. Uses 1 buffer. Perfect for oscillator values, volume displays, and momentum bars.
#property indicator_type1 DRAW_HISTOGRAM
#property indicator_color1 clrGreen
#property indicator_width1 3
DRAW_HISTOGRAM2 — Vertical bars between two values. Uses 2 buffers. Great for showing ranges and fill areas.
DRAW_ARROW — Draws symbols at specific points. Uses 1 buffer. Ideal for buy/sell signals, entry/exit markers.
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrLime
// Set the arrow code in OnInit:
PlotIndexSetInteger(0, PLOT_ARROW, 233); // up arrow
// Common codes: 233=up arrow, 234=down arrow, 159=dot, 108=star
DRAW_COLOR_LINE — A line that changes color based on conditions. Uses 2 buffers (data + color index). Powerful for showing trend direction with color.
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_type1 DRAW_COLOR_LINE
#property indicator_color1 clrLime,clrRed,clrGray // up to 64 colors
#property indicator_width1 2
Multi-Color Line Example
A moving average that is green when price is above it and red when below:
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_type1 DRAW_COLOR_LINE
#property indicator_color1 clrLime,clrRed
#property indicator_width1 2
input int MAPeriod = 20;
double maBuffer[];
double colorBuffer[]; // 0=green(above), 1=red(below)
int OnInit()
{
SetIndexBuffer(0, maBuffer, INDICATOR_DATA);
SetIndexBuffer(1, colorBuffer, INDICATOR_COLOR_INDEX);
return INIT_SUCCEEDED;
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
int start = (prev_calculated == 0) ? MAPeriod : prev_calculated - 1;
for(int i = start; i < rates_total; i++)
{
// Calculate SMA
double sum = 0;
for(int j = 0; j < MAPeriod; j++)
sum += close[i - j];
maBuffer[i] = sum / MAPeriod;
// Set color: 0=green if price above MA, 1=red if below
colorBuffer[i] = (close[i] >= maBuffer[i]) ? 0 : 1;
}
return rates_total;
}
Buy/Sell Signal Arrows
Using DRAW_ARROW to mark entry points:
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrLime
#property indicator_width1 2
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrRed
#property indicator_width2 2
double buySignal[];
double sellSignal[];
int OnInit()
{
SetIndexBuffer(0, buySignal, INDICATOR_DATA);
SetIndexBuffer(1, sellSignal, INDICATOR_DATA);
// Arrow codes: 233=up arrow (buy), 234=down arrow (sell)
PlotIndexSetInteger(0, PLOT_ARROW, 233);
PlotIndexSetInteger(1, PLOT_ARROW, 234);
// Show arrows slightly offset from price
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
return INIT_SUCCEEDED;
}
In your OnCalculate, set buySignal[i] = low[i] - 10*_Point when a buy condition is met (places arrow below the candle), and sellSignal[i] = high[i] + 10*_Point for sells (above the candle). Set to 0 (empty value) when no signal.
Separate Window Indicators
For oscillators and indicators that do not overlay on the price chart:
#property indicator_separate_window // draw in separate sub-window
#property indicator_minimum 0 // fix Y-axis minimum
#property indicator_maximum 100 // fix Y-axis maximum
#property indicator_level1 30 // horizontal reference line
#property indicator_level2 70 // horizontal reference line
#property indicator_levelcolor clrSilver
Use Indicator Buffers the Way the Terminal Expects, in Five Steps
A buffer is just an array — but it is an array the terminal owns, keeps between calls, and may draw. Three consequences follow, and each one produces a distinctive bug: an indicator that recomputes the whole chart on every tick and makes the platform crawl, an indicator whose hidden working values reset unpredictably, and an indicator that squashes the price into an unreadable strip the moment you attach it. In the next half hour you will measure all three. 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. If you already have it from an earlier page, reuse that
copy and skip to step 3.
// 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).
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.
Go: the same folder.
Do: save this as build.sh. It compiles a .mq5 file and executes the
result. Make it executable with chmod +x build.sh, or invoke it as
sh 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 — this is the toolchain. ls should show both
mql5.h and build.sh.
If not: Permission denied means the chmod was skipped; use
sh build.sh NAME, which needs no execute bit.
Go: the same folder.
Do: save this as prevcalc.mq5 and run sh build.sh prevcalc.
#include "mql5.h"
//+------------------------------------------------------------------+
//| prev_calculated is why an indicator does not recompute 100,000 |
//| bars on every tick -- and why getting it wrong is invisible. |
//+------------------------------------------------------------------+
input int MaPeriod = 3;
std::vector<double> MaBuffer;
long total_iterations = 0; // just to count the work
int Calculate(const int rates_total, const int prev_calculated,
const std::vector<double> &close, bool use_prev)
{
ArrayResize(MaBuffer, rates_total);
int start = MaPeriod - 1;
if(use_prev && prev_calculated > 0)
start = prev_calculated - 1; // recompute only the last bar onward
for(int i = start; i < rates_total; i++)
{
double sum = 0.0;
for(int k = 0; k < MaPeriod; k++) { sum += close[i - k]; total_iterations++; }
MaBuffer[i] = sum / MaPeriod;
}
return rates_total;
}
void OnStart()
{
std::vector<double> close;
for(int i = 0; i < 1000; i++) close.push_back(100.0 + (i % 7));
// 50 ticks arrive on the same chart.
total_iterations = 0;
int prev = 0;
for(int tick = 0; tick < 50; tick++)
prev = Calculate(ArraySize(close), prev, close, false); // ignores prev
long naive = total_iterations;
total_iterations = 0;
prev = 0;
for(int tick = 0; tick < 50; tick++)
prev = Calculate(ArraySize(close), prev, close, true); // uses prev
long smart = total_iterations;
Print("bars on the chart : ", ArraySize(close));
Print("ticks processed : 50");
Print("");
Print("ignoring prev_calculated : ", naive, " inner iterations");
Print("using prev_calculated : ", smart, " inner iterations");
Print("ratio : ", (int)(naive / (smart > 0 ? smart : 1)), "x more work");
Print("");
Print("On a 1000-bar chart that is merely wasteful. On a 100,000-bar chart");
Print("with several indicators it is why the terminal stops responding --");
Print("and nothing errors, so it looks like the platform is slow.");
}
int main() { OnStart(); return 0; }
You should see: the same 50 ticks doing 47 times as much work:
bars on the chart : 1000
ticks processed : 50
ignoring prev_calculated : 149700 inner iterations
using prev_calculated : 3141 inner iterations
ratio : 47x more work
On a 1000-bar chart that is merely wasteful. On a 100,000-bar chart
with several indicators it is why the terminal stops responding --
and nothing errors, so it looks like the platform is slow.
The terminal passes prev_calculated so you can start where you left
off. Ignore it and you recalculate every bar on the chart on every tick — correct results,
catastrophic cost.
The reason this bug survives so long is that nothing reports it. The indicator is right, the chart looks fine, and the only symptom is that the terminal becomes sluggish with several charts open — which people attribute to the platform, their computer, or their broker. A 1000-bar chart hides it; a 100,000-bar chart with four indicators does not.
The one subtlety: start from prev_calculated - 1, not
prev_calculated. The last bar you calculated was the forming bar, and it has changed
since — so it must be recomputed, and only it.
If not: if both counts are equal, the use_prev argument is being passed the same
way in both loops. If the ratio is far larger or smaller than 47, the bar count or tick count was
changed, which moves it proportionally.
Go: the same folder.
Do: save this as buffers.mq5 and run sh build.sh buffers.
#include "mql5.h"
//+------------------------------------------------------------------+
//| Not every buffer is drawn. The ones that are not do the work. |
//+------------------------------------------------------------------+
// A MACD needs four arrays but shows two lines.
std::vector<double> MacdLine; // INDICATOR_DATA -- drawn
std::vector<double> SignalLine; // INDICATOR_DATA -- drawn
std::vector<double> FastEma; // INDICATOR_CALCULATIONS -- hidden
std::vector<double> SlowEma; // INDICATOR_CALCULATIONS -- hidden
void Ema(const std::vector<double> &src, std::vector<double> &dst, int period, int total)
{
ArrayResize(dst, total);
double k = 2.0 / (period + 1);
double seed = 0.0;
for(int i = 0; i < period; i++) seed += src[i];
seed /= period;
for(int i = 0; i < period - 1; i++) dst[i] = 0.0;
dst[period - 1] = seed;
for(int i = period; i < total; i++)
dst[i] = src[i] * k + dst[i - 1] * (1 - k);
}
void OnStart()
{
std::vector<double> close;
for(int i = 0; i < 40; i++) close.push_back(100.0 + (i % 9) - (i % 5));
int total = ArraySize(close);
Ema(close, FastEma, 5, total);
Ema(close, SlowEma, 12, total);
ArrayResize(MacdLine, total);
for(int i = 0; i < total; i++)
MacdLine[i] = (i < 11) ? 0.0 : FastEma[i] - SlowEma[i];
Ema(MacdLine, SignalLine, 4, total);
Print("buffers declared : 4");
Print("lines drawn : 2 (indicator_plots)");
Print("");
Print("bar FastEma SlowEma MACD Signal");
for(int i = 30; i < 35; i++)
Print(" ", i, " ", DoubleToString(FastEma[i], 4),
" ", DoubleToString(SlowEma[i], 4),
" ", DoubleToString(MacdLine[i], 4),
" ", DoubleToString(SignalLine[i], 4));
Print("");
Print("FastEma and SlowEma are never shown, but they must be BUFFERS --");
Print("not local variables -- because their previous values are needed on");
Print("the next call, and a local array is gone the moment OnCalculate");
Print("returns.");
Print("");
Print("In MetaEditor:");
Print(" #property indicator_buffers 4 <- how many arrays exist");
Print(" #property indicator_plots 2 <- how many are drawn");
Print(" SetIndexBuffer(0, MacdLine, INDICATOR_DATA);");
Print(" SetIndexBuffer(2, FastEma, INDICATOR_CALCULATIONS);");
Print("");
Print("Declaring 2 buffers and using 4 arrays is the classic mistake:");
Print("it compiles, and the two extra arrays are not preserved between");
Print("calls, so the indicator's values drift or reset unpredictably.");
}
int main() { OnStart(); return 0; }
You should see: four arrays producing two lines:
buffers declared : 4
lines drawn : 2 (indicator_plots)
bar FastEma SlowEma MACD Signal
30 100.9864 101.4532 -0.4668 -0.4816
31 101.6576 101.6912 -0.0336 -0.3024
32 102.1051 101.8925 0.2125 -0.0964
33 102.4034 102.0629 0.3405 0.0783
34 102.6022 102.2071 0.3952 0.2051
FastEma and SlowEma are never shown, but they must be BUFFERS --
not local variables -- because their previous values are needed on
the next call, and a local array is gone the moment OnCalculate
returns.
In MetaEditor:
#property indicator_buffers 4 <- how many arrays exist
#property indicator_plots 2 <- how many are drawn
SetIndexBuffer(0, MacdLine, INDICATOR_DATA);
SetIndexBuffer(2, FastEma, INDICATOR_CALCULATIONS);
Declaring 2 buffers and using 4 arrays is the classic mistake:
it compiles, and the two extra arrays are not preserved between
calls, so the indicator's values drift or reset unpredictably.
indicator_buffers and indicator_plots are different
numbers and both matter. The first says how many arrays the terminal must preserve between calls;
the second says how many of them to draw.
The intermediate arrays cannot be local variables, because an EMA needs its own
previous value and a local array ceases to exist when OnCalculate returns. Declaring
them as buffers with INDICATOR_CALCULATIONS tells the terminal to keep them without
drawing them.
The failure mode is worth recognising: declare indicator_buffers 2 while using four
arrays and it compiles cleanly, the chart looks right at first, and the values drift or reset when
the terminal recalculates — because two of your arrays were never preserved.
If not: if the MACD column is zero throughout, the guard i < 11 is excluding
every bar — it must skip only the bars before the slower EMA exists, which with a 12-period
EMA is the first eleven.
Go: the same folder.
Do: save this as emptyvalue.mq5 and run sh build.sh emptyvalue.
#include "mql5.h"
//+------------------------------------------------------------------+
//| What to put in a buffer position that has no value. |
//+------------------------------------------------------------------+
const double EMPTY = 0.0; // what many people use
// MQL5's own answer: PLOT_EMPTY_VALUE, usually EMPTY_VALUE (a huge number),
// which tells the terminal "draw nothing here" instead of "draw a zero".
void OnStart()
{
// A 5-bar average on 8 bars: the first 4 positions have no value.
std::vector<double> close = {100, 102, 101, 103, 105, 104, 106, 108};
std::vector<double> with_zero(8), with_empty(8);
for(int i = 0; i < 8; i++)
{
if(i < 4) { with_zero[i] = 0.0; with_empty[i] = 1e308; continue; }
double s = 0.0;
for(int k = 0; k < 5; k++) s += close[i - k];
with_zero[i] = with_empty[i] = s / 5.0;
}
Print("bar close buffer filled with 0 buffer left EMPTY");
for(int i = 0; i < 8; i++)
Print(" ", i, " ", DoubleToString(close[i], 1),
" ", DoubleToString(with_zero[i], 2),
" ", with_empty[i] > 1e300 ? "(not drawn)"
: DoubleToString(with_empty[i], 2));
Print("");
double lo = with_zero[0], hi = with_zero[0];
for(int i = 0; i < 8; i++) { lo = MathMin(lo, with_zero[i]); hi = MathMax(hi, with_zero[i]); }
Print("chart scale if zeros are drawn : ", DoubleToString(lo, 1),
" to ", DoubleToString(hi, 1));
lo = hi = with_empty[4];
for(int i = 4; i < 8; i++) { lo = MathMin(lo, with_empty[i]); hi = MathMax(hi, with_empty[i]); }
Print("chart scale if they are not : ", DoubleToString(lo, 1),
" to ", DoubleToString(hi, 1));
Print("");
Print("A zero in a price buffer is not 'no value' -- it is the price zero.");
Print("The terminal draws a line plunging to the bottom of the window and");
Print("rescales the whole chart around it, which is why a new indicator");
Print("sometimes flattens the price into an unreadable strip.");
Print("");
Print("Set PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE) in OnInit,");
Print("and write EMPTY_VALUE into positions you have not calculated.");
}
int main() { OnStart(); return 0; }
You should see: the same data producing a chart scale of 0–105 or 102–105:
bar close buffer filled with 0 buffer left EMPTY
0 100.0 0.00 (not drawn)
1 102.0 0.00 (not drawn)
2 101.0 0.00 (not drawn)
3 103.0 0.00 (not drawn)
4 105.0 102.20 102.20
5 104.0 103.00 103.00
6 106.0 103.80 103.80
7 108.0 105.20 105.20
chart scale if zeros are drawn : 0.0 to 105.2
chart scale if they are not : 102.2 to 105.2
A zero in a price buffer is not 'no value' -- it is the price zero.
The terminal draws a line plunging to the bottom of the window and
rescales the whole chart around it, which is why a new indicator
sometimes flattens the price into an unreadable strip.
Set PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE) in OnInit,
and write EMPTY_VALUE into positions you have not calculated.
This is the bug behind a very recognisable experience: you attach a new indicator and the price candles collapse into a thin band at the top of the window. The indicator wrote zeros into the bars it had not calculated, the terminal drew them as the price zero, and rescaled the window to fit.
Zero is a price, not an absence. MQL5 has a separate concept for “nothing
here”: set PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE) in
OnInit() and write EMPTY_VALUE into uncalculated positions. The terminal
then skips them when drawing and when scaling.
It matters beyond appearance. Another indicator or an EA reading your buffer sees those zeros too, and a comparison like “is price above my line?” is true for every zero — which is the same class of failure as the ignored return value on the functions page.
If not: if the two scale lines are identical, the with_empty array is being
filled with zeros as well — the sentinel must be the very large value, which is what stands in
for EMPTY_VALUE here.
Without scrolling up: your indicator draws correctly on a fresh chart, but after scrolling
back through a few years of history the terminal becomes unusably slow, and after switching
timeframe some of the older values are wrong. Name the two separate causes and the fix for each. Answer: the slowness is prev_calculated being ignored: step 3 measured 47 times more
work on a 1000-bar chart, and scrolling back loads far more bars, so the cost grows with the
history you have loaded rather than with anything you did. The fix is to begin the loop at
prev_calculated - 1 rather than at the start of the array. The wrong older values are a
different problem — almost certainly intermediate arrays that were not declared as buffers, so
they are not preserved when the terminal recalculates after a timeframe switch, and any calculation
that depends on its own previous value restarts from whatever happened to be in memory. The fix is
to declare every array the calculation depends on in indicator_buffers and register the
non-drawn ones with INDICATOR_CALCULATIONS. Both bugs are silent, which is why they
usually arrive together and get blamed on the platform.
Now do it without the page: take buffers.mq5 and deliberately make FastEma a local
array inside a function rather than a file-level buffer, then call the calculation twice in a row
and compare the second result with the first. Watching the values change when nothing about the
input changed is the clearest way to understand what “the terminal preserves buffers”
actually buys you. Then add EMPTY_VALUE handling from step 5 to
sma_ind.mq5 from the previous page, so the two habits arrive together.
Buffer Management Tips
- Buffers used for drawing must come first, calculation-only buffers last
- Use
INDICATOR_CALCULATIONSfor helper buffers that should not appear in the Data Window - Use
PlotIndexSetDouble(index, PLOT_EMPTY_VALUE, 0)to define what "no data" looks like — the indicator skips drawing at those points - Set
IndicatorSetString(INDICATOR_SHORTNAME, "Name")so your indicator shows a meaningful name in the chart and Data Window