Complete .mq5 source code

Keltner Channel MQL5 Indicator: ATR Bands and Completed-Bar Alerts

Plot a configurable moving average with upper and lower bands at a selected ATR multiple. Defaults are EMA 20, ATR 10 and 1.5 times ATR. The supplied custom indicator draws three price-chart lines, exposes normalized width in the Data Window and provides completed-bar breakout/return alerts. It contains no companion trading EA or verified performance record.

How the indicator signal works

Calculation and signal conditions

Middle is the selected moving average of the selected price; upper and lower are middle plus/minus the ATR multiplier. Buffer 3 reports (upper minus lower) divided by the absolute middle value times 100, unless the denominator is zero. Breakout alerts compare two completed closes with their own contemporaneous bands. There is no ATR-expansion, spread or account-equity filter; an alert is not an entry order.

Interpretation and limitations

The optional return alert identifies a completed close back inside the channel after the prior close was outside. It can be enabled independently from breakout alerts. The indicator has no exits, stop-loss, trailing, drawdown guard or trade execution. ATR and standard deviation measure different features of prices; neither establishes fewer false breaks without a defined comparison. Forming-bar bands can change, and parameter values do not guarantee a fixed price-coverage percentage.

MQL5 Custom Indicator Code

//+------------------------------------------------------------------+
//|  KeltnerChannel.mq5                                                |
//|  Keltner Channel MQL5 Indicator — ATR-Based Volatility Channel    |
//|  Middle: EMA(20) | Bands: +/- 1.5 x ATR(10)                       |
//|  Educational indicator; no verified strategy backtest supplied.  |
//+------------------------------------------------------------------+
#property copyright "Pineify — pineify.app"
#property link      "https://pineify.app"
#property version   "1.01"
#property strict

//--- Indicator chart properties
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4

//--- Plot 1 — Middle EMA band
#property indicator_label1  "KC_Middle"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- Plot 2 — Upper band
#property indicator_label2  "KC_Upper"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrangeRed
#property indicator_style2  STYLE_DOT
#property indicator_width2  1

//--- Plot 3 — Lower band
#property indicator_label3  "KC_Lower"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrLimeGreen
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

//--- Plot 4 — normalized width exposed without distorting the price scale
#property indicator_label4  "KC_Width"
#property indicator_type4   DRAW_NONE
#property indicator_color4  clrGray
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1

//--- Input parameters: indicator settings
input int      InpMAPeriod       = 20;         // EMA period for middle line
input int      InpATRPeriod      = 10;         // ATR period for band width
input double   InpMultiplier     = 1.5;        // ATR multiplier (upper/lower)
input ENUM_MA_METHOD InpMAMethod = MODE_EMA;   // Moving average type
input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; // Price for middle line

//--- Input parameters: alerts
input bool     InpAlertOnBreak   = true;       // Alert on price breaking bands
input bool     InpAlertOnReturn  = false;      // Alert on price returning inside

//--- Indicator buffers
double         g_midBuffer[];   // index 0 — middle EMA line
double         g_upBuffer[];    // index 1 — upper band
double         g_loBuffer[];    // index 2 — lower band
double         g_widthBuffer[]; // index 3 — band width (upper - lower)

//--- Handle variables for underlying indicators
int            g_maHandle  = INVALID_HANDLE;
int            g_atrHandle = INVALID_HANDLE;

//--- State for cross-detection alerts
datetime       g_lastAlertTime = 0;

//+------------------------------------------------------------------+
//| Indicator initialization function                                 |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpMAPeriod<1 || InpATRPeriod<1 || InpMultiplier<=0) return INIT_PARAMETERS_INCORRECT;
   //--- Bind buffers to their indices (INDICATOR_DATA = plot data)
   SetIndexBuffer(0, g_midBuffer,   INDICATOR_DATA);
   SetIndexBuffer(1, g_upBuffer,    INDICATOR_DATA);
   SetIndexBuffer(2, g_loBuffer,    INDICATOR_DATA);
   SetIndexBuffer(3, g_widthBuffer, INDICATOR_DATA);
   ArraySetAsSeries(g_midBuffer,false); ArraySetAsSeries(g_upBuffer,false);
   ArraySetAsSeries(g_loBuffer,false); ArraySetAsSeries(g_widthBuffer,false);
   for(int p=0;p<4;p++) PlotIndexSetDouble(p,PLOT_EMPTY_VALUE,EMPTY_VALUE);

   //--- Label each plot for the DataWindow tooltip
   PlotIndexSetString(0, PLOT_LABEL, "KC_Mid("   + IntegerToString(InpMAPeriod) + ")");
   PlotIndexSetString(1, PLOT_LABEL, "KC_Upper("  + IntegerToString(InpMAPeriod) + ")");
   PlotIndexSetString(2, PLOT_LABEL, "KC_Lower("  + IntegerToString(InpMAPeriod) + ")");
   PlotIndexSetString(3, PLOT_LABEL, "KC_Width");

   //--- Set indicator short name and number of decimals
   IndicatorSetString(INDICATOR_SHORTNAME,
      "KeltnerCh(" + IntegerToString(InpMAPeriod) + "," +
      IntegerToString(InpATRPeriod) + "," +
      DoubleToString(InpMultiplier, 1) + ")");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

   //--- Create iMA handle for the middle line
   g_maHandle = iMA(_Symbol, _Period, InpMAPeriod, 0, InpMAMethod, InpPrice);
   if(g_maHandle == INVALID_HANDLE)
     {
      Print("FATAL [OnInit]: iMA handle creation failed. Error: ", GetLastError());
      return INIT_FAILED;
     }

   //--- Create iATR handle for band-width calculation
   g_atrHandle = iATR(_Symbol, _Period, InpATRPeriod);
   if(g_atrHandle == INVALID_HANDLE)
     {
      Print("FATAL [OnInit]: iATR handle creation failed. Error: ", GetLastError());
      return INIT_FAILED;
     }

   //--- DRAW_BEGIN: skip bars before both indicators have enough data
   int drawBegin = MathMax(InpMAPeriod-1, InpATRPeriod);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, drawBegin);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, drawBegin);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, drawBegin);
   PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, drawBegin);

   Print("Keltner Channel initialised: ", _Symbol, " ", EnumToString(_Period),
         " | MA=", InpMAPeriod, " ATR=", InpATRPeriod, " Mult=", InpMultiplier);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Indicator calculation function — fills all four output buffers    |
//+------------------------------------------------------------------+
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[])
  {
   //--- Minimum bars required: both MA and ATR need warm-up data
   int minBars = MathMax(InpMAPeriod, InpATRPeriod) + 2;
   if(rates_total < minBars)
      return 0;

   ArraySetAsSeries(time,false); ArraySetAsSeries(close,false);
   int warmup=MathMax(InpMAPeriod-1,InpATRPeriod);
   int start;
   if(prev_calculated==0 || prev_calculated>rates_total) {
      ArrayInitialize(g_midBuffer,EMPTY_VALUE); ArrayInitialize(g_upBuffer,EMPTY_VALUE);
      ArrayInitialize(g_loBuffer,EMPTY_VALUE); ArrayInitialize(g_widthBuffer,EMPTY_VALUE);
      g_lastAlertTime=0;
      start=warmup;
   } else start=MathMax(warmup,prev_calculated-1);
   // Default chronological arrays match the output buffers.
   double maBuf[],atrBuf[];
   ArraySetAsSeries(maBuf,false); ArraySetAsSeries(atrBuf,false);

   //--- Copy MA and ATR values from their indicator handles
   int copiedMA  = CopyBuffer(g_maHandle,  0, 0, rates_total, maBuf);
   int copiedATR = CopyBuffer(g_atrHandle, 0, 0, rates_total, atrBuf);
   if(copiedMA < rates_total || copiedATR < rates_total)
     {
      Print("WARNING: Buffer copy incomplete. MA=", copiedMA, " ATR=", copiedATR);
      return 0;
     }

   //--- Compute Keltner Channel values bar by bar
   for(int i = start; i < rates_total; i++)
     {
      double ma  = maBuf[i];
      double atr = atrBuf[i];

      g_midBuffer[i]   = ma;                   // middle = EMA
      g_upBuffer[i]    = ma + (InpMultiplier * atr); // upper band
      g_loBuffer[i]    = ma - (InpMultiplier * atr); // lower band
      g_widthBuffer[i] = ma!=0 ? (g_upBuffer[i]-g_loBuffer[i])/MathAbs(ma)*100.0 : EMPTY_VALUE; // width %
     }

   //--- Alert on band break (only if enabled and on a new bar)
   if((InpAlertOnBreak || InpAlertOnReturn) && prev_calculated > 0 && rates_total>=warmup+3)
     {
      int last = rates_total - 2; // completed bar; last-1 is the preceding completed bar
      if(time[last] != g_lastAlertTime)
        {
         g_lastAlertTime = time[last];

         double closeLast  = close[last];
         double closePrev  = close[last - 1];

         //--- Price crossed above upper band
         if(InpAlertOnBreak && closeLast > g_upBuffer[last] && closePrev <= g_upBuffer[last - 1])
            Alert("KC BreakOUT: ", _Symbol, " closed above upper band at ",
                  DoubleToString(closeLast, _Digits));

         //--- Price crossed below lower band
         if(InpAlertOnBreak && closeLast < g_loBuffer[last] && closePrev >= g_loBuffer[last - 1])
            Alert("KC BreakDOWN: ", _Symbol, " closed below lower band at ",
                  DoubleToString(closeLast, _Digits));

         //--- Price returned inside the channel (optional alert)
         if(InpAlertOnReturn)
           {
            if(closeLast <= g_upBuffer[last] && closeLast >= g_loBuffer[last])
              {
               if(closePrev > g_upBuffer[last - 1] || closePrev < g_loBuffer[last - 1])
                  Alert("KC Return: ", _Symbol, " price re-entered the channel.");
              }
           }
        }
     }

   return rates_total;
  }

//+------------------------------------------------------------------+
//| Indicator deinitialization function — release handles             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_maHandle  != INVALID_HANDLE) IndicatorRelease(g_maHandle);
   if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
   Print("Keltner Channel deinitialised. Reason: ", reason);
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor, save it in the MQL5/Indicators folder, and compile with F7.

Change the settings or add your own signal filter

Describe the inputs, buffers, alerts, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.

Frequently Asked Questions

Related MQL5 pages

Technical references

Risk and testing note

These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.