We previously published an MT4 Keltner Channel; here’s the modern MQL5 version, using MT5’s built-in indicator handles for the EMA and ATR so the code stays short and correct. See the explainer for the concept.
What you’ll need
- MetaTrader 5 with MetaEditor
- Save as
AlgoGen.Keltner.mq5underMQL5/Indicators/
The source
//+------------------------------------------------------------------+
//| AlgoGen.Keltner.mq5 |
//| Copyright 2026, AlgoGen |
//| https://www.algogen.io |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, AlgoGen"
#property link "https://www.algogen.io"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
#property indicator_label1 "Middle"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrOrangeRed
#property indicator_width1 2
#property indicator_label2 "Upper"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrMediumPurple
#property indicator_label3 "Lower"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrMediumPurple
input int InpEmaPeriod = 20;
input int InpAtrPeriod = 10;
input double InpMult = 2.0;
double MidBuffer[], UpBuffer[], LoBuffer[];
int emaHandle, atrHandle;
int OnInit()
{
SetIndexBuffer(0, MidBuffer, INDICATOR_DATA);
SetIndexBuffer(1, UpBuffer, INDICATOR_DATA);
SetIndexBuffer(2, LoBuffer, INDICATOR_DATA);
emaHandle = iMA(_Symbol, _Period, InpEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
if(emaHandle == INVALID_HANDLE || atrHandle == INVALID_HANDLE)
return(INIT_FAILED);
IndicatorSetString(INDICATOR_SHORTNAME, "AlgoGen Keltner");
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 need = MathMax(InpEmaPeriod, InpAtrPeriod);
if(rates_total < need) return(0);
double ema[], atr[];
if(CopyBuffer(emaHandle, 0, 0, rates_total, ema) <= 0) return(prev_calculated);
if(CopyBuffer(atrHandle, 0, 0, rates_total, atr) <= 0) return(prev_calculated);
int start = (prev_calculated > 0) ? prev_calculated - 1 : need;
for(int i = start; i < rates_total; i++)
{
MidBuffer[i] = ema[i];
UpBuffer[i] = ema[i] + InpMult * atr[i];
LoBuffer[i] = ema[i] - InpMult * atr[i];
}
return(rates_total);
}
//+------------------------------------------------------------------+
Install & compile
- Drop it in
MQL5/Indicators/, press F7. - Drag onto a chart — three lines overlay price, matching the output chart.
Gotchas
- Handles +
CopyBuffer. ReusingiMA(MODE_EMA)andiATRguarantees your EMA and ATR match MT5’s own, so your channel is standard. Copying full arrays each pass is simple; cache for performance in production. - EMA vs SMA centerline.
MODE_EMAgives the modern Keltner; the 1960 original used an SMA of typical price with a range-based band — a different indicator. - MT4 version. Our earlier MT4 post computes the bands with an EMA/ATR directly in MQL4; the math is the same, only the API differs.
Same indicator elsewhere: Python, Pine Script, EasyLanguage, NinjaScript. Then test a Keltner breakout in AlgoGen.
This post is educational, not financial advice. Indicators describe the past; they don’t predict the future. Backtest anything before you risk real money on it.
Sources and further reading
- Technical indicator functions (MetaQuotes)
