Complete .mq5 source code

MQL5 Mean Reversion Strategy: Bollinger/RSI EA and Test Boundaries

This educational chart-symbol EA combines completed-bar Bollinger Band and RSI conditions with an initial ATR-based stop and a fixed midpoint target. It supplies source and a rule-by-rule explanation, not a verified multi-pair backtest. A price outside a band is not proof of mispricing or a coming reversal; the parameters need independent behavior and cost tests.

Strategy logic

Entry conditions

At a new chart bar, a completed close at or below the lower band with RSI below the lower threshold is a long condition; a close at or above the upper band with RSI above its upper threshold is a short condition. The defaults are 20-period bands with deviation 2 and RSI(14) thresholds 35/65. The EA allows no additional entry while any matching symbol/magic position remains. Lot sizing approximates a percentage of current equity using tick value and stop distance, excludes execution costs, and skips when the computed size falls below the broker minimum.

Exit conditions

The target is the completed-bar middle band captured when the entry request is made; it does not follow the moving average afterward. A buy stop is ATR_Multiplier times ATR below the entry ask, and a sell stop is that distance above the entry bid, not beyond the candle high or low. On new bars, matching buys request closure when completed RSI is at least 50 and sells when it is at most 50; this is a level test, not an explicit crossing test. Initial targets on the wrong side of the entry quote are rejected. There is no trailing, ADX regime filter, news filter, or portfolio loss controller. Stops can execute beyond their level during gaps, and FOK must be supported by the broker. Symbol/magic filtering cannot isolate other systems sharing the same netting position.

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()
  {
   if(BB_Period <= 0 || BB_Deviation <= 0 || RSI_Period <= 0 || ATR_Period <= 0 ||
      RSI_OversoldLevel < 0 || RSI_OverboughtLevel > 100 ||
      RSI_OversoldLevel >= 50 || RSI_OverboughtLevel <= 50 ||
      RiskPercent <= 0 || RiskPercent > 100 || ATR_Multiplier <= 0 || MagicNumber < 0)
      return INIT_PARAMETERS_INCORRECT;
   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 <= 0 || currentBarTime == lastBarTime) return;

   //--- Copy indicator values (use bar index 1 = last closed bar)
   if(CopyBuffer(hBB,  1,  0, 3, bbUpper)  < 3) return;
   if(CopyBuffer(hBB,  0,   0, 3, bbMiddle) < 3) return;
   if(CopyBuffer(hBB,  2,  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];

   if(closePrice <= 0 || atr <= 0 || atr == EMPTY_VALUE) return;
   lastBarTime = currentBarTime;
   //--- Manage open positions using completed RSI level
   ManageOpenPositions(rsi);

   //--- Check if we already have an open position
   for(int i = PositionsTotal()-1; i >= 0; i--)
      if(PositionGetTicket(i) > 0 && PositionGetString(POSITION_SYMBOL) == _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 && tp > ask)
         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 && tp < bid)
         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) ||
      (result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_DONE_PARTIAL))
      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 rsi)
  {
   for(int i = PositionsTotal()-1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0 || PositionGetString(POSITION_SYMBOL) != _Symbol ||
         PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      bool exit = type == POSITION_TYPE_BUY ? rsi >= 50 : rsi <= 50;
      if(!exit) continue;
      MqlTradeRequest request = {};
      MqlTradeResult result = {};
      request.action = TRADE_ACTION_DEAL;
      request.position = ticket;
      request.symbol = _Symbol;
      request.volume = PositionGetDouble(POSITION_VOLUME);
      request.type = type == POSITION_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      request.price = type == 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) || result.retcode != TRADE_RETCODE_DONE)
         Print("Early exit not completed: ", result.retcode, " ", result.comment);
     }
  }

//+------------------------------------------------------------------+
//| 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;
   if(tickSize <= 0 || tickValue <= 0 || lotStep <= 0 || riskAmount <= 0) return 0;
   double valuePerLot = (slDistance / tickSize) * tickValue;
   if(valuePerLot <= 0.0) return 0.0;

   double lots = riskAmount / valuePerLot;
   lots = MathFloor(MathMin(maxLot, lots) / lotStep) * lotStep;
   if(lots < minLot) return 0;
   return NormalizeDouble(lots, 8);
  }
//+------------------------------------------------------------------+

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 entry, exit, position, order, and risk rules 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 accessStrategy orders are simulated; live automation needs a separate 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.

Frequently Asked Questions

Related MQL5 pages

Risk and testing note

These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.