Problem Description
Solution
Here is a complete, fully commented Expert Advisor (EA) for MetaTrader 4 that implements a Bollinger Bands breakout strategy.
Strategy Logic
- Buy Signal: The previous candle's Close price is higher than the Upper Bollinger Band.
- Sell Signal: The previous candle's Close price is lower than the Lower Bollinger Band.
- Execution: The EA checks conditions only once per bar (on the open of a new candle) to prevent false signals during price fluctuations within the candle duration.
- Trade Management: If a Buy signal occurs while a Sell trade is open, the Sell trade is closed immediately, and vice versa.
MQL4 Code
//+------------------------------------------------------------------+
//| BollingerBreakout_EA.mq4 |
//| Copyright 2023, Quantitative Trading AI |
//| https://mql4.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative Trading AI"
#property link "https://mql4.com"
#property version "1.00"
#property strict
//--- Input Parameters
input double InpLots = 0.1; // Lot size
input int InpStopLoss = 50; // Stop Loss in points (0 = no SL)
input int InpTakeProfit = 100; // Take Profit in points (0 = no TP)
input int InpMagicNum = 123456; // Magic Number to identify orders
input int InpSlippage = 3; // Maximum slippage in points
//--- Bollinger Bands Settings
input int InpBandsPeriod = 20; // Bollinger Bands Period
input int InpBandsShift = 0; // Bollinger Bands Shift
input double InpBandsDev = 2.0; // Bollinger Bands Deviation
//--- Global Variables
datetime glbLastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Basic validation
if(InpBandsPeriod <= 0)
{
Print("Error: Bands Period must be greater than 0");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Check for New Bar (Run logic only once per candle close)
if(glbLastBarTime == Time[0]) return;
glbLastBarTime = Time[0];
// 2. Get Indicator Values (Shift 1 = Previous closed candle)
double upperBand = iBands(_Symbol, _Period, InpBandsPeriod, InpBandsDev, InpBandsShift, PRICE_CLOSE, MODE_UPPER, 1);
double lowerBand = iBands(_Symbol, _Period, InpBandsPeriod, InpBandsDev, InpBandsShift, PRICE_CLOSE, MODE_LOWER, 1);
double prevClose = Close[1];
// 3. Check for Buy Signal (Breakout Above)
if(prevClose > upperBand)
{
// Close existing Sell positions first
ClosePositions(OP_SELL);
// Open Buy if no Buy exists
if(CountOrders(OP_BUY) == 0)
{
OpenTrade(OP_BUY);
}
}
// 4. Check for Sell Signal (Breakout Below)
if(prevClose < lowerBand)
{
// Close existing Buy positions first
ClosePositions(OP_BUY);
// Open Sell if no Sell exists
if(CountOrders(OP_SELL) == 0)
{
OpenTrade(OP_SELL);
}
}
}
//+------------------------------------------------------------------+
//| Helper: Open a Trade |
//+------------------------------------------------------------------+
void OpenTrade(int type)
{
double price, sl, tp;
color cmdColor;
if(type == OP_BUY)
{
price = Ask;
// Calculate SL/TP for Buy
sl = (InpStopLoss > 0) ? price - InpStopLoss * _Point : 0;
tp = (InpTakeProfit > 0) ? price + InpTakeProfit * _Point : 0;
cmdColor = clrBlue;
}
else
{
price = Bid;
// Calculate SL/TP for Sell
sl = (InpStopLoss > 0) ? price + InpStopLoss * _Point : 0;
tp = (InpTakeProfit > 0) ? price - InpTakeProfit * _Point : 0;
cmdColor = clrRed;
}
// Normalize prices to avoid Error 130 (Invalid Stops)
price = NormalizeDouble(price, _Digits);
sl = NormalizeDouble(sl, _Digits);
tp = NormalizeDouble(tp, _Digits);
int ticket = OrderSend(_Symbol, type, InpLots, price, InpSlippage, sl, tp, "BB Breakout", InpMagicNum, 0, cmdColor);
if(ticket < 0)
{
Print("OrderSend failed with error #", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Helper: Close specific position types |
//+------------------------------------------------------------------+
void ClosePositions(int type)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNum)
{
if(OrderType() == type)
{
bool result = false;
if(type == OP_BUY)
result = OrderClose(OrderTicket(), OrderLots(), Bid, InpSlippage, clrNONE);
else if(type == OP_SELL)
result = OrderClose(OrderTicket(), OrderLots(), Ask, InpSlippage, clrNONE);
if(!result) Print("Failed to close order: ", GetLastError());
}
}
}
}
}
//+------------------------------------------------------------------+
//| Helper: Count specific order types |
//+------------------------------------------------------------------+
int CountOrders(int type)
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNum)
{
if(OrderType() == type) count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
How to Install and Use
- Open MetaTrader 4.
- Press F4 to open the MetaEditor.
- Click New -> Expert Advisor (template) -> Name it "BollingerBreakout".
- Delete everything in the new file and paste the code provided above.
- Press F7 to Compile. Ensure there are no errors in the "Errors" tab at the bottom.
- Return to the main MT4 window.
- Find "BollingerBreakout" in the Navigator panel under Expert Advisors.
- Drag it onto a chart (e.g., EURUSD H1).
- Ensure AutoTrading is enabled in the top toolbar.
Key Features of this Code
- New Bar Check: The logic
if(glbLastBarTime == Time[0]) return;ensures the EA only trades once per candle. This is crucial for breakout strategies to confirm the candle actually closed outside the band. - Risk Management: It includes inputs for Stop Loss and Take Profit. If you set them to 0, the EA will rely solely on the opposite signal to close the trade.
- Magic Number: Uses a unique ID (
InpMagicNum) so it doesn't accidentally close trades opened by you manually or by other EAs. - Normalization: Uses
NormalizeDoubleon prices to prevent "Invalid Price" errors common with 5-digit brokers.
Q&A: Bollinger Bands Breakout Strategy
Q: Why does the EA wait for the candle to close before trading?
A: Waiting for the close confirms the breakout. If the price spikes above the band but retraces before the candle closes, it leaves a "wick." Trading immediately on the touch (tick-by-tick) often results in false breakouts and buying at the very top before a reversal.
Q: Can I use this on any timeframe?
A: Yes. The EA uses _Period, which automatically detects the timeframe of the chart you attach it to. Bollinger Band breakouts are typically more reliable on H1, H4, or Daily charts.
Q: What happens if the price stays outside the bands for multiple candles?
A: The code checks if(CountOrders(OP_BUY) == 0). If a Buy trade is already open and the price stays above the upper band, it will simply hold the existing position and will not open multiple trades for the same trend.
Q: How do I optimize the parameters?
A: In the MT4 Strategy Tester, you can select "Optimization". You can then set ranges for InpBandsPeriod (e.g., 10 to 50) and InpBandsDev (e.g., 1.5 to 3.0) to find the best combination for a specific currency pair.