If you've spent any time on Reddit's algo trading communities, you've probably seen the posts: "Built an ORB EA for MT5 – What strategies am I missing?" The Opening Range Breakout is one of those strategies that sounds deceptively simple – wait for the first X minutes of a session, draw a high and low, then trade the breakout. Simple in theory, but the devil lives in session time handling, broker GMT offsets, and stop loss placement that doesn't get you stopped out before the move begins.
I've been coding MQL4 EAs for over a decade, and I'll tell you straight: most ORB implementations I've seen on forums are either too rigid (hardcoded times that break when DST changes) or too loose (no real risk management). This post walks you through building a proper ORB EA MQL4 that handles London and New York sessions correctly, adjusts for your broker's timezone, and uses a dynamic stop loss that adapts to market conditions. By the end, you'll have a working EA template you can test, break, and improve.
What Is the Opening Range Breakout Strategy?
The ORB strategy originated with traders like Toby Crabel, who observed that the first 30–60 minutes of a trading session often establishes a price range that, when broken, leads to a directional move for the rest of the session. In its purest form: you identify a session (e.g., London open at 08:00 GMT), record the high and low of the first N minutes (the opening range), then place a buy stop above the high and a sell stop below the low. Whichever gets triggered first becomes your trade direction.
For forex, the two most liquid sessions are London (08:00–17:00 GMT) and New York (13:00–22:00 GMT). Overlap between 13:00–17:00 GMT sees the highest volume, but the ORB works best when you focus on the session open rather than the overlap. I've found that London session breakouts tend to be cleaner because the range is established after the Asian session's drift, and New York session breakouts capture the US equity market open momentum.
Why MQL4 for ORB?
MT4 is still the most widely used platform for retail forex, and MQL4's simplicity lets you prototype session logic quickly. The main challenge is handling time correctly – brokers use different GMT offsets (GMT+2, GMT+3, etc.) and DST rules. A hardcoded "08:00" will fail when your broker switches to summer time. Our EA will use a GMT offset input so you can adjust without recompiling.
Building the ORB EA in MQL4: Step by Step
Let's get into the code. I'll assume you know basic MQL4 – functions, orders, and the OnTick() structure. If you're new, this will still make sense conceptually; just follow the comments.
Step 1: Define Session Times and Inputs
We need inputs for session start, opening range duration, GMT offset, and trade parameters. Here's my preferred set:
| Parameter | Type | Default | Description |
|---|---|---|---|
| SessionChoice | int | 0 | 0=London, 1=New York, 2=Both |
| LondonStartHour | int | 8 | London open hour (GMT) |
| NYStartHour | int | 13 | NY open hour (GMT) |
| ORBMinutes | int | 30 | Opening range duration in minutes |
| GMT_Offset | int | 2 | Broker GMT offset (e.g., 2 for GMT+2) |
| StopLossType | int | 0 | 0=fixed pips, 1=ATR-based, 2=range-based |
| FixedSL | int | 30 | Fixed stop loss in pips |
| ATR_Period | int | 14 | ATR period for dynamic SL |
| ATR_Multiplier | double | 1.5 | Multiplier for ATR-based SL |
The GMT_Offset parameter is critical. Most brokers use server time that's GMT+2 or GMT+3 during summer. If you set London start to 8 and your broker is GMT+2, the actual broker time for London open is 10:00. You can find your broker's offset by comparing server time to GMT – I usually check TimeGMT() vs TimeCurrent() in a script.
Step 2: Identify the Opening Range
When a new session starts, we need to track the high and low of the first ORBMinutes. The key is to only calculate this once per session. Here's the logic I use in OnTick():
// Global variables
datetime lastSessionTime = 0;
double orbHigh = 0, orbLow = 0;
bool orbReady = false;
int orbBarCount = 0;
// Inside OnTick()
datetime currentTime = TimeCurrent();
int currentHour = Hour();
int currentMinute = Minute();
// Adjust for broker GMT offset
int gmtHour = currentHour - GMT_Offset;
if(gmtHour < 0) gmtHour += 24;
// Check if we're at session start
bool londonStart = (gmtHour == LondonStartHour && currentMinute == 0);
bool nyStart = (gmtHour == NYStartHour && currentMinute == 0);
if((londonStart || nyStart) && lastSessionTime != currentTime){
orbHigh = High[0];
orbLow = Low[0];
orbBarCount = 0;
orbReady = false;
lastSessionTime = currentTime;
}
// Accumulate opening range
if(lastSessionTime != 0 && !orbReady){
orbBarCount++;
if(High[0] > orbHigh) orbHigh = High[0];
if(Low[0] < orbLow) orbLow = Low[0];
// Check if ORB period is over (in minutes)
if(orbBarCount >= ORBMinutes){
orbReady = true;
// Place pending orders
PlaceORBOrders();
}
}This snippet assumes a 1-minute chart. If you're using a 5-minute chart, adjust ORBMinutes accordingly – 30 minutes on M1 means 30 bars, on M5 it's 6 bars. I prefer M1 for precision, but M5 reduces noise from wicks.
Step 3: Place Pending Orders with Dynamic Stop Loss
Once the opening range is established, we place a buy stop above the high and a sell stop below the low. The stop loss depends on your StopLossType:
- Fixed pips: Simple – SL = entry price ± FixedSL * Point * 10 (for 5-digit brokers).
- ATR-based: Calculate ATR on the M1 chart (or higher timeframe) and multiply by
ATR_Multiplier. This adapts to volatility – tight ranges get tight stops, wide ranges get wider stops. - Range-based: SL = the opposite side of the opening range (e.g., for a long, SL = orbLow – buffer). This is the most intuitive but can be too tight if the range is narrow.
Here's the order placement function:
void PlaceORBOrders(){
double slPoints;
if(StopLossType == 0){
slPoints = FixedSL * Point * 10;
} else if(StopLossType == 1){
double atr = iATR(NULL, PERIOD_M1, ATR_Period, 0);
slPoints = atr * ATR_Multiplier;
} else {
// Range-based: use range size
slPoints = (orbHigh - orbLow) * 0.5; // 50% of range
}
// Buy stop
double buyEntry = orbHigh + (2 * Point); // small buffer above high
double buySL = buyEntry - slPoints;
double buyTP = buyEntry + (slPoints * 2); // 1:2 risk-reward
int ticket = OrderSend(Symbol(), OP_BUYSTOP, LotSize, buyEntry, 3, buySL, buyTP, "ORB Buy", MagicNumber, 0, Green);
// Sell stop
double sellEntry = orbLow - (2 * Point);
double sellSL = sellEntry + slPoints;
double sellTP = sellEntry





