Generate code in 3 minutes
Enter your idea and AI generates runnable code.
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).
//+------------------------------------------------------------------+
//| 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());
}
}
}
}
//+------------------------------------------------------------------+
F4 to open the MetaEditor.F7 or click Compile. Ensure there are no errors.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);
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.
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).
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: 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.