Problem Description
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
- Middle Line: Exponential Moving Average (EMA) of the Close price.
- Upper Band: Middle Line + (ATR * Multiplier).
- Lower Band: Middle Line - (ATR * Multiplier).
- Buy Signal: When the previous candle closes above the Upper Band.
- Sell Signal: When the previous candle closes below the Lower Band.
- 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
- Open MetaTrader 4.
- Press
F4to open the MetaEditor. - Click New -> Expert Advisor (template) -> Name it "KeltnerBot".
- Delete everything in the new file and paste the code above.
- Press
F7or click Compile. Ensure there are no errors. - Go back to the MT4 terminal. The bot will appear in the Navigator window under Expert Advisors.
- Drag the bot onto a chart (e.g., EURUSD H1).
Key Code Components Explained
-
Manual Calculation:
Since there is noiKeltnerfunction, we calculate the bands insideOnTick:double maValue = iMA(..., KeltnerPeriod, ...); double atrValue = iATR(..., KeltnerPeriod, ...); double upperBand = maValue + (atrValue * KeltnerMultiplier); -
New Bar Check:
The lineif(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. -
Digit Normalization:
Theg_pointvariable andNormalizeDoublefunctions ensure the bot works correctly on both 4-digit and 5-digit brokers (e.g., JPY pairs vs USD pairs). -
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.