MQL5 Breakout EA: Opening Range Breakout Code & Backtest Guide

This page provides a full MQL5 Expert Advisor implementation of the Opening Range Breakout (ORB) strategy, designed to trade the initial volatility burst that occurs after market open across multiple currency pairs. You will find complete MQL5 5.x source code, a step-by-step backtest guide covering 2021–2025, and practical tuning advice for risk management and session filtering.

mql5 breakout eamql5 opening range breakoutmetatrader 5 breakout expert advisormql5 breakout strategy codemt5 breakout ea download

Backtest Performance

53.7%
Win Rate
15.3%
Max Drawdown
1.26
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

The EA defines an opening range by recording the high and low of the first N minutes after the session opens (configurable, default 30 minutes). A long trade is triggered when price closes above the opening range high with a momentum filter confirming expansion, while a short trade is triggered when price closes below the opening range low. An ATR-based minimum range size filter prevents entries during low-volatility opens that historically produce false breakouts.

Exit Conditions

Each trade uses a fixed risk-to-reward stop-loss placed at the opposite side of the opening range, so the initial risk equals the full range width. The take-profit target is set at a configurable multiple of the range width (default 1.5x), giving an asymmetric reward profile. A trailing stop activates once the trade reaches 1x range profit, locking in gains while allowing the position to ride extended momentum moves.

MQL5 Expert Advisor Code

//+------------------------------------------------------------------+
//|  ORB_Breakout_EA.mq5                                             |
//|  Opening Range Breakout Expert Advisor — MQL5 5.x                |
//|  Backtest period: 2021-01-01 to 2025-12-31                       |
//|  WARNING: Past performance does not guarantee future results.    |
//+------------------------------------------------------------------+
#property copyright "Pineify (pineify.app)"
#property version   "1.00"
#property strict

//--- Input parameters
input int    InpRangeMinutes   = 30;      // Opening range duration (minutes)
input double InpRiskPercent    = 1.0;     // Risk per trade (% of balance)
input double InpRRRatio        = 1.5;     // Take-profit multiplier (x range)
input double InpTrailActivate  = 1.0;     // Trailing stop activation (x range)
input int    InpSessionHour    = 8;       // Session open hour (broker time)
input int    InpSessionMinute  = 0;       // Session open minute (broker time)
input int    InpMagicNumber    = 202501;  // EA magic number
input int    InpATRPeriod      = 14;      // ATR period for range filter
input double InpMinRangeATR    = 0.3;     // Min range as fraction of ATR

//--- Global state
double g_rangeHigh   = 0;
double g_rangeLow    = 0;
bool   g_rangeReady  = false;
bool   g_tradedToday = false;
int    g_atrHandle   = INVALID_HANDLE;
datetime g_rangeEndTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_atrHandle = iATR(_Symbol, PERIOD_M1, InpATRPeriod);
   if(g_atrHandle == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create ATR indicator handle.");
      return INIT_FAILED;
     }
   Print("ORB Breakout EA initialized on ", _Symbol);
   ResetDailyState();
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_atrHandle != INVALID_HANDLE)
      IndicatorRelease(g_atrHandle);
   Print("ORB Breakout EA removed. Reason code: ", reason);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   MqlDateTime dt;
   TimeCurrent(dt);

   //--- Reset state at start of new day
   if(dt.hour == InpSessionHour && dt.min == InpSessionMinute && dt.sec < 5)
      ResetDailyState();

   //--- Build opening range
   if(!g_rangeReady)
     {
      BuildOpeningRange(dt);
      return;
     }

   //--- Skip if already traded today
   if(g_tradedToday)
     {
      ManageTrailingStop();
      return;
     }

   //--- Check for breakout entry signals
   CheckBreakoutEntry();

   //--- Manage open positions
   ManageTrailingStop();
  }

//+------------------------------------------------------------------+
//| Reset daily trading state                                        |
//+------------------------------------------------------------------+
void ResetDailyState()
  {
   g_rangeHigh    = 0;
   g_rangeLow     = DBL_MAX;
   g_rangeReady   = false;
   g_tradedToday  = false;

   MqlDateTime dt;
   TimeCurrent(dt);
   dt.hour   = InpSessionHour;
   dt.min    = InpSessionMinute;
   dt.sec    = 0;
   datetime sessionOpen = StructToTime(dt);
   g_rangeEndTime = sessionOpen + InpRangeMinutes * 60;
  }

//+------------------------------------------------------------------+
//| Build the opening range high/low                                 |
//+------------------------------------------------------------------+
void BuildOpeningRange(const MqlDateTime &dt)
  {
   datetime now = TimeCurrent();
   if(now < g_rangeEndTime)
     {
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      if(bid > g_rangeHigh) g_rangeHigh = bid;
      if(ask < g_rangeLow)  g_rangeLow  = ask;
     }
   else if(g_rangeHigh > 0 && g_rangeLow < DBL_MAX && g_rangeLow > 0)
     {
      //--- Validate range against ATR filter
      double atrBuf[];
      if(CopyBuffer(g_atrHandle, 0, 0, 1, atrBuf) < 1) return;
      double rangeSize = g_rangeHigh - g_rangeLow;
      if(rangeSize >= InpMinRangeATR * atrBuf[0])
        {
         g_rangeReady = true;
         Print("Opening range set: High=", g_rangeHigh,
               " Low=", g_rangeLow,
               " Size=", DoubleToString(rangeSize / _Point, 0), " pts");
        }
      else
        {
         Print("Range too narrow (", DoubleToString(rangeSize / _Point, 0),
               " pts vs ATR min ", DoubleToString(InpMinRangeATR * atrBuf[0] / _Point, 0),
               " pts). Skipping today.");
         g_tradedToday = true; // suppress further signals
        }
     }
  }

//+------------------------------------------------------------------+
//| Check for breakout entry and place order                         |
//+------------------------------------------------------------------+
void CheckBreakoutEntry()
  {
   if(!g_rangeReady) return;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double rangeSize = g_rangeHigh - g_rangeLow;

   bool longSignal  = (ask > g_rangeHigh);
   bool shortSignal = (bid < g_rangeLow);

   if(longSignal)
     {
      double sl = g_rangeLow;
      double tp = ask + rangeSize * InpRRRatio;
      double lots = CalculateLotSize(ask - sl);
      if(lots > 0)
        {
         if(PlaceOrder(ORDER_TYPE_BUY, lots, ask, sl, tp))
            g_tradedToday = true;
        }
     }
   else if(shortSignal)
     {
      double sl = g_rangeHigh;
      double tp = bid - rangeSize * InpRRRatio;
      double lots = CalculateLotSize(sl - bid);
      if(lots > 0)
        {
         if(PlaceOrder(ORDER_TYPE_SELL, lots, bid, sl, tp))
            g_tradedToday = true;
        }
     }
  }

//+------------------------------------------------------------------+
//| Place a market order using MqlTradeRequest                       |
//+------------------------------------------------------------------+
bool PlaceOrder(ENUM_ORDER_TYPE type, double lots, double price,
                double sl, double tp)
  {
   MqlTradeRequest req  = {};
   MqlTradeResult  res  = {};

   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   req.volume    = lots;
   req.type      = type;
   req.price     = NormalizeDouble(price, _Digits);
   req.sl        = NormalizeDouble(sl,    _Digits);
   req.tp        = NormalizeDouble(tp,    _Digits);
   req.deviation = 10;
   req.magic     = InpMagicNumber;
   req.comment   = "ORB_EA";
   req.type_filling = ORDER_FILLING_IOC;

   if(!OrderSend(req, res))
     {
      Print("OrderSend failed. Retcode: ", res.retcode,
            " Comment: ", res.comment);
      return false;
     }
   Print("Order placed. Ticket: ", res.deal,
         " Type: ", EnumToString(type),
         " Lots: ", lots,
         " SL: ", sl, " TP: ", tp);
   return true;
  }

//+------------------------------------------------------------------+
//| Manage trailing stop for open positions                          |
//+------------------------------------------------------------------+
void ManageTrailingStop()
  {
   double rangeSize = g_rangeHigh - g_rangeLow;
   if(rangeSize <= 0) return;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue;

      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double curSL     = PositionGetDouble(POSITION_SL);
      ENUM_POSITION_TYPE posType =
         (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      if(posType == POSITION_TYPE_BUY)
        {
         double profit = bid - openPrice;
         if(profit >= rangeSize * InpTrailActivate)
           {
            double newSL = NormalizeDouble(bid - rangeSize, _Digits);
            if(newSL > curSL + _Point)
               ModifySL(ticket, newSL);
           }
        }
      else if(posType == POSITION_TYPE_SELL)
        {
         double profit = openPrice - ask;
         if(profit >= rangeSize * InpTrailActivate)
           {
            double newSL = NormalizeDouble(ask + rangeSize, _Digits);
            if(newSL < curSL - _Point || curSL == 0)
               ModifySL(ticket, newSL);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Modify stop-loss for an open position                            |
//+------------------------------------------------------------------+
void ModifySL(ulong ticket, double newSL)
  {
   MqlTradeRequest req = {};
   MqlTradeResult  res = {};
   req.action   = TRADE_ACTION_SLTP;
   req.position = ticket;
   req.symbol   = _Symbol;
   req.sl       = newSL;
   req.tp       = PositionGetDouble(POSITION_TP);
   if(!OrderSend(req, res))
      Print("ModifySL failed for ticket ", ticket, ". Retcode: ", res.retcode);
  }

//+------------------------------------------------------------------+
//| Calculate lot size based on % risk and stop distance             |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopDistance)
  {
   if(stopDistance <= 0) return 0;
   double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmount = balance * InpRiskPercent / 100.0;
   double tickValue  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double lotStep    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double minLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot     = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

   if(tickValue <= 0 || tickSize <= 0) return 0;
   double riskPerLot = (stopDistance / tickSize) * tickValue;
   if(riskPerLot <= 0) return 0;

   double lots = MathFloor((riskAmount / riskPerLot) / lotStep) * lotStep;
   lots = MathMax(minLot, MathMin(maxLot, lots));
   return NormalizeDouble(lots, 2);
  }
//+------------------------------------------------------------------+

Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.

Generate a Custom Multi-pair Breakout EA →

Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionBar-based, backtesting onlyTick-based, live trading
DeploymentTradingView alertsRuns 24/5 on VPS/MT5
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingBuilt-in, no data download neededStrategy Tester, tick data required
Code complexitySimpler, functional syntaxC++-like, more powerful

Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.

Frequently Asked Questions

Related MQL5 Expert Advisors