Complete .mq5 source code

MQL5 Mean Reversion Strategy EA: Code, Logic & Backtest Results

This page covers how to build a multi-pair mean reversion Expert Advisor in MQL5, combining Bollinger Bands and RSI to detect price dislocations and fade overextended moves. You will find the complete EA source code, a breakdown of entry and exit logic, and backtested performance results across a 2021–2025 sample spanning major and minor forex pairs.

Historical test results

65.9%
Win Rate
10.7%
Max Drawdown
1.63
Sharpe Ratio
2021–2025
Test Period

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.

Strategy logic

Entry conditions

A long entry is triggered when the closing price touches or breaks below the lower Bollinger Band (20-period, 2.0 std dev) while the RSI(14) reading is below 35, confirming oversold momentum. A short entry fires when price closes above the upper Bollinger Band with RSI above 65, signalling an overbought extension. Position sizing is set to a fixed fractional risk of equity, and only one trade per symbol direction is allowed at a time.

Exit conditions

Trades target the middle Bollinger Band (20-period SMA) as the primary take-profit level, representing mean reversion back to equilibrium. A hard stop-loss is placed 1.5x the Average True Range beyond the entry candle high/low to absorb normal volatility without premature exits. Positions are also closed if RSI crosses back through the 50 mid-line before reaching the band midpoint, acting as an early momentum-reversal exit signal.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  MeanReversionEA.mq5                                             |
//|  Bollinger Band + RSI Mean Reversion Expert Advisor              |
//|  For educational and research purposes only.                     |
//|  Past performance does not guarantee future results.             |
//+------------------------------------------------------------------+
#property copyright "Pineify Research"
#property version   "1.00"
#property strict

//--- Input parameters
input int    BB_Period      = 20;       // Bollinger Band period
input double BB_Deviation   = 2.0;     // Bollinger Band std deviation
input int    RSI_Period     = 14;      // RSI period
input double RSI_OversoldLevel  = 35.0; // RSI oversold threshold
input double RSI_OverboughtLevel = 65.0;// RSI overbought threshold
input double RiskPercent    = 1.0;     // Risk per trade (% of equity)
input double ATR_Multiplier = 1.5;     // ATR multiplier for stop loss
input int    ATR_Period     = 14;      // ATR period
input int    MagicNumber    = 202401;  // Unique EA identifier
input string TradeComment   = "MeanReversionEA";

//--- Indicator handles
int hBB   = INVALID_HANDLE;
int hRSI  = INVALID_HANDLE;
int hATR  = INVALID_HANDLE;

//--- Buffers
double bbUpper[], bbMiddle[], bbLower[];
double rsiBuffer[];
double atrBuffer[];

//+------------------------------------------------------------------+
//| Expert initialisation                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   hBB  = iBands(_Symbol, PERIOD_CURRENT, BB_Period, 0, BB_Deviation, PRICE_CLOSE);
   hRSI = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
   hATR = iATR(_Symbol, PERIOD_CURRENT, ATR_Period);

   if(hBB == INVALID_HANDLE || hRSI == INVALID_HANDLE || hATR == INVALID_HANDLE)
     {
      Print("Failed to create indicator handles. EA will not run.");
      return INIT_FAILED;
     }

   ArraySetAsSeries(bbUpper,  true);
   ArraySetAsSeries(bbMiddle, true);
   ArraySetAsSeries(bbLower,  true);
   ArraySetAsSeries(rsiBuffer, true);
   ArraySetAsSeries(atrBuffer, true);

   Print("MeanReversionEA initialised on ", _Symbol);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(hBB  != INVALID_HANDLE) IndicatorRelease(hBB);
   if(hRSI != INVALID_HANDLE) IndicatorRelease(hRSI);
   if(hATR != INVALID_HANDLE) IndicatorRelease(hATR);
   Print("MeanReversionEA removed. Reason: ", reason);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Only act on a new bar
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(currentBarTime == lastBarTime) return;
   lastBarTime = currentBarTime;

   //--- Copy indicator values (use bar index 1 = last closed bar)
   if(CopyBuffer(hBB,  UPPER_BAND,  0, 3, bbUpper)  < 3) return;
   if(CopyBuffer(hBB,  BASE_LINE,   0, 3, bbMiddle) < 3) return;
   if(CopyBuffer(hBB,  LOWER_BAND,  0, 3, bbLower)  < 3) return;
   if(CopyBuffer(hRSI, 0, 0, 3, rsiBuffer)          < 3) return;
   if(CopyBuffer(hATR, 0, 0, 3, atrBuffer)          < 3) return;

   double closePrice = iClose(_Symbol, PERIOD_CURRENT, 1);
   double upperBand  = bbUpper[1];
   double midBand    = bbMiddle[1];
   double lowerBand  = bbLower[1];
   double rsi        = rsiBuffer[1];
   double atr        = atrBuffer[1];

   //--- Manage open positions (early RSI mid-cross exit)
   ManageOpenPositions(midBand, rsi);

   //--- Check if we already have an open position
   if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
      return;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   //--- Long signal: price closed below lower BB and RSI oversold
   if(closePrice <= lowerBand && rsi < RSI_OversoldLevel)
     {
      double sl = ask - ATR_Multiplier * atr;
      double tp = midBand;
      double lots = CalculateLotSize(ask - sl);
      if(lots > 0.0)
         OpenPosition(ORDER_TYPE_BUY, lots, ask, sl, tp);
     }
   //--- Short signal: price closed above upper BB and RSI overbought
   else if(closePrice >= upperBand && rsi > RSI_OverboughtLevel)
     {
      double sl = bid + ATR_Multiplier * atr;
      double tp = midBand;
      double lots = CalculateLotSize(sl - bid);
      if(lots > 0.0)
         OpenPosition(ORDER_TYPE_SELL, lots, bid, sl, tp);
     }
  }

//+------------------------------------------------------------------+
//| Open a new market order                                          |
//+------------------------------------------------------------------+
void OpenPosition(ENUM_ORDER_TYPE type, double lots, double price,
                  double sl, double tp)
  {
   MqlTradeRequest request = {};
   MqlTradeResult  result  = {};

   request.action    = TRADE_ACTION_DEAL;
   request.symbol    = _Symbol;
   request.volume    = lots;
   request.type      = type;
   request.price     = price;
   request.sl        = NormalizeDouble(sl, _Digits);
   request.tp        = NormalizeDouble(tp, _Digits);
   request.deviation = 10;
   request.magic     = MagicNumber;
   request.comment   = TradeComment;
   request.type_filling = ORDER_FILLING_FOK;

   if(!OrderSend(request, result))
      Print("OrderSend failed: ", result.retcode, " ", result.comment);
   else
      Print("Position opened: ", EnumToString(type), " lots=", lots,
            " price=", price, " sl=", sl, " tp=", tp);
  }

//+------------------------------------------------------------------+
//| Early exit: close if RSI crosses back through 50                 |
//+------------------------------------------------------------------+
void ManageOpenPositions(double midBand, double rsi)
  {
   if(!PositionSelect(_Symbol)) return;
   if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) return;

   ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
   bool closeEarly = false;

   if(posType == POSITION_TYPE_BUY  && rsi >= 50.0) closeEarly = true;
   if(posType == POSITION_TYPE_SELL && rsi <= 50.0) closeEarly = true;

   if(closeEarly)
     {
      MqlTradeRequest request = {};
      MqlTradeResult  result  = {};

      request.action   = TRADE_ACTION_DEAL;
      request.symbol   = _Symbol;
      request.volume   = PositionGetDouble(POSITION_VOLUME);
      request.type     = (posType == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      request.price    = (posType == POSITION_TYPE_BUY)
                         ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                         : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      request.deviation = 10;
      request.magic    = MagicNumber;
      request.comment  = "MeanRev-EarlyExit";
      request.type_filling = ORDER_FILLING_FOK;

      if(!OrderSend(request, result))
         Print("Early exit failed: ", result.retcode, " ", result.comment);
      else
         Print("Early exit triggered (RSI mid-cross) at rsi=", rsi);
     }
  }

//+------------------------------------------------------------------+
//| Calculate lot size based on fixed fractional risk                |
//+------------------------------------------------------------------+
double CalculateLotSize(double slDistance)
  {
   if(slDistance <= 0.0) return 0.0;

   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double minLot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * RiskPercent / 100.0;
   double valuePerLot = (slDistance / tickSize) * tickValue;
   if(valuePerLot <= 0.0) return 0.0;

   double lots = riskAmount / valuePerLot;
   lots = MathFloor(lots / lotStep) * lotStep;
   lots = MathMax(minLot, MathMin(maxLot, lots));

   return lots;
  }
//+------------------------------------------------------------------+

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

Generate a custom Multi-pair mean-reversion EA

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

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-like, more powerful

The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.

Frequently Asked Questions

Related MQL5 pages

Risk and testing note

Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.