🚀 AI One-Click Generation of mql4 Strategy Code

How to Build a Keltner Channel Trading EA in Mql4

mql4 | Published on: 2025/12/15
The following content is generated by EasyQuant .

Problem Description

I need a trading bot that uses Keltner Channels. Can you show me how to code it?

Solution

Here is a complete, fully functional Expert Advisor (EA) for MetaTrader 4 that implements a Keltner Channel Breakout Strategy.

Since MQL4 does not have a built-in Keltner Channel indicator function, this code manually calculates the Upper, Middle, and Lower bands using Moving Averages (iMA) and Average True Range (iATR).

Strategy Logic

  1. Middle Line: Exponential Moving Average (EMA) of the Close price.
  2. Upper Band: Middle Line + (ATR * Multiplier).
  3. Lower Band: Middle Line - (ATR * Multiplier).
  4. Buy Signal: When the previous candle closes above the Upper Band.
  5. Sell Signal: When the previous candle closes below the Lower Band.
  6. Exit: Based on Stop Loss and Take Profit settings.

MQL4 Code

//+------------------------------------------------------------------+
//|                                       KeltnerChannelBreakout.mq4 |
//|                        Copyright 2023, Quantitative Trading AI   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Trading AI"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Input Parameters
input string   Inp_Settings      = "--- Strategy Settings ---"; // .
input int      KeltnerPeriod     = 20;       // MA and ATR Period
input double   KeltnerMultiplier = 2.0;      // ATR Multiplier for Bands
input ENUM_MA_METHOD MA_Method   = MODE_EMA; // Moving Average Type
input double   LotSize           = 0.1;      // Fixed Lot Size
input int      StopLoss          = 50;       // Stop Loss in pips (0 = none)
input int      TakeProfit        = 100;      // Take Profit in pips (0 = none)
input int      MagicNumber       = 123456;   // Magic Number to identify trades
input int      Slippage          = 3;        // Max Slippage in pips

//--- Global Variables
double g_point; // Adjusted point value for 3/5 digit brokers

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Adjust point for 5-digit brokers (0.00001) vs 4-digit (0.0001)
   if(Digits == 3 || Digits == 5) g_point = Point * 10;
   else g_point = Point;

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // 1. Check if we have enough bars to calculate
   if(Bars < KeltnerPeriod) return;

   // 2. Check if a new bar has formed (Run logic only once per bar close)
   static datetime lastBarTime = 0;
   if(Time[0] == lastBarTime) return;
   lastBarTime = Time[0];

   // 3. Calculate Keltner Channel Values for the PREVIOUS candle (Shift 1)
   // Middle Line (Moving Average)
   double maValue = iMA(NULL, 0, KeltnerPeriod, 0, MA_Method, PRICE_CLOSE, 1);
   
   // ATR Value
   double atrValue = iATR(NULL, 0, KeltnerPeriod, 1);
   
   // Upper and Lower Bands
   double upperBand = maValue + (atrValue * KeltnerMultiplier);
   double lowerBand = maValue - (atrValue * KeltnerMultiplier);

   // 4. Get Previous Close Price
   double prevClose = Close[1];

   // 5. Check for Open Trades managed by this EA
   int openBuy = 0;
   int openSell = 0;
   
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
         {
            if(OrderType() == OP_BUY) openBuy++;
            if(OrderType() == OP_SELL) openSell++;
         }
      }
   }

   // 6. Trading Logic
   
   // BUY SIGNAL: Close price breaks above Upper Band
   if(openBuy == 0 && prevClose > upperBand)
   {
      // Close any existing Sell positions first (Reverse strategy)
      if(openSell > 0) CloseAllPositions(OP_SELL);
      
      // Open Buy
      OpenTrade(OP_BUY);
   }

   // SELL SIGNAL: Close price breaks below Lower Band
   if(openSell == 0 && prevClose < lowerBand)
   {
      // Close any existing Buy positions first (Reverse strategy)
      if(openBuy > 0) CloseAllPositions(OP_BUY);
      
      // Open Sell
      OpenTrade(OP_SELL);
   }
}

//+------------------------------------------------------------------+
//| Helper: Open Trade Function                                      |
//+------------------------------------------------------------------+
void OpenTrade(int type)
{
   double price, sl, tp;
   color arrowColor;

   if(type == OP_BUY)
   {
      price = Ask;
      sl = (StopLoss > 0) ? price - StopLoss * g_point : 0;
      tp = (TakeProfit > 0) ? price + TakeProfit * g_point : 0;
      arrowColor = clrBlue;
   }
   else // OP_SELL
   {
      price = Bid;
      sl = (StopLoss > 0) ? price + StopLoss * g_point : 0;
      tp = (TakeProfit > 0) ? price - TakeProfit * g_point : 0;
      arrowColor = clrRed;
   }

   // Normalize prices to prevent Error 130 (Invalid Stops)
   price = NormalizeDouble(price, Digits);
   sl = NormalizeDouble(sl, Digits);
   tp = NormalizeDouble(tp, Digits);

   int ticket = OrderSend(Symbol(), type, LotSize, price, Slippage, sl, tp, "Keltner Bot", MagicNumber, 0, arrowColor);
   
   if(ticket < 0)
   {
      Print("OrderSend failed with error #", GetLastError());
   }
}

//+------------------------------------------------------------------+
//| Helper: Close All Positions of specific type                     |
//+------------------------------------------------------------------+
void CloseAllPositions(int type)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
         {
            bool res = false;
            if(type == OP_BUY) 
               res = OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrGray);
            else 
               res = OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrGray);
               
            if(!res) Print("OrderClose failed: ", GetLastError());
         }
      }
   }
}
//+------------------------------------------------------------------+

How to Install and Use

  1. Open MetaTrader 4.
  2. Press F4 to open the MetaEditor.
  3. Click New -> Expert Advisor (template) -> Name it "KeltnerBot".
  4. Delete everything in the new file and paste the code above.
  5. Press F7 or click Compile. Ensure there are no errors.
  6. Go back to the MT4 terminal. The bot will appear in the Navigator window under Expert Advisors.
  7. Drag the bot onto a chart (e.g., EURUSD H1).

Key Code Components Explained

  1. Manual Calculation:
    Since there is no iKeltner function, we calculate the bands inside OnTick:

    double maValue = iMA(..., KeltnerPeriod, ...);
    double atrValue = iATR(..., KeltnerPeriod, ...);
    double upperBand = maValue + (atrValue * KeltnerMultiplier);
    
  2. New Bar Check:
    The line if(Time[0] == lastBarTime) return; ensures the bot only checks logic once per candle close. This is crucial for breakout strategies to prevent the bot from opening multiple trades on the same candle if the price flickers around the band.

  3. Digit Normalization:
    The g_point variable and NormalizeDouble functions ensure the bot works correctly on both 4-digit and 5-digit brokers (e.g., JPY pairs vs USD pairs).

  4. Trade Management:
    The bot includes logic to close opposite trades. If a Buy signal occurs while a Sell trade is open, it will close the Sell trade immediately before opening the Buy trade.

Q&A: Keltner Channel Bot

Q: How do I change the sensitivity of the channel?
A: Adjust the KeltnerMultiplier input. A higher value (e.g., 2.5 or 3.0) creates wider bands, resulting in fewer but potentially higher-quality breakout signals. A lower value (e.g., 1.5) results in more frequent trades.

Q: Can I use this for Mean Reversion (buying at the bottom, selling at the top)?
A: This specific code is a Breakout (Trend Following) bot. To make it Mean Reversion, you would simply swap the logic in the OnTick function: Buy when prevClose < lowerBand and Sell when prevClose > upperBand.

Q: Why does the bot not open trades immediately?
A: The bot waits for a confirmed close. It checks Close[1] (the previous completed candle). It will not trade until a candle fully closes outside the channel bands.