Viewing the resource: Understanding and Implementing the "Asian Session Breakout EA"

Understanding and Implementing the "Asian Session Breakout EA"

Allan Munene Mutiiria 2025-06-03 22:31:13 589 Views
The "Asian Session Breakout EA" is a beginner-friendly MQL5 strategy for MetaTrader 5. It targets br...

Introduction

Welcome, new traders, to the "Asian Session Breakout EA"! This MQL5 strategy for MetaTrader 5 is perfect for beginners. We focus on the Asian session (23:00 to 03:00), a quiet market time, to find a price range—high and low—then place breakout orders to catch big moves. A "50-period SMA" filters trades, and we add stop-loss, take-profit, and optional trailing stops. This article explains every step and code section in detail.

Understanding the Strategy

For beginners, a breakout strategy means we wait for the price to burst out of a range. The Asian session (23:00 to 03:00) often has low movement, so we mark the highest and lowest prices in this time on a 15-minute chart ("PERIOD_M15"). If the price breaks above the high and is above the "50-period SMA", we set a buy stop order; if below the low and under the "SMA", a sell stop order. We offset entries by "BreakoutOffsetPoints" (10 points) for confirmation, set stop-losses at the opposite range, and take-profits at 1.3x the risk ("risktoreward"). Trades close by 13:00, and an optional trailing stop can follow the price. Here is a visualization.

Implementation Code

Below is the MQL5 code for the "Asian Session Breakout EA". We’ll dive into big chunks, explaining each part clearly for newbies, connecting the flow from setup to trading and management.

//+------------------------------------------------------------------+
//|                                    Asian Session Breakout EA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

int mahandle = INVALID_HANDLE;

input double LotSize = 0.1;
input double BreakoutOffsetPoints = 10;
input ENUM_TIMEFRAMES BoxTimeframe = PERIOD_M15;
input int ma_period = 50;
input ENUM_MA_METHOD ma_method = MODE_SMA;
input ENUM_APPLIED_PRICE ma_app_price = PRICE_CLOSE;
input double risktoreward = 1.3;
input int magicNumber = 12345;
input bool isApplyTrailingStop = false;

input int sessionStartHour = 23;
input int sessionStartMin = 00;
input int sessionEndHour = 03;
input int sessionEndMin = 00;
input int tradeExitHour = 13;
input int tradeExitMin = 00;

datetime lastBoxSessionEnd = 0;
bool boxCalculated = false;
bool ordersPlaced = false;
double BoxHigh = 0.0;
double BoxLow = 0.0;

datetime BoxHighTime = 0;
datetime BoxLowTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   obj_Trade.SetExpertMagicNumber(magicNumber);
   
   mahandle = iMA(_Symbol,_Period,ma_period,0,MODE_SMA,ma_app_price);
   
   if (mahandle == INVALID_HANDLE){
      Print("ERROR: FAILED TO INITIALIZE THE MA HANDLE. REVERTING NOW!!!");
      return (INIT_FAILED);
   }
   
//---
   return(INIT_SUCCEEDED);
}

Let’s begin with setup! The "#property" lines label our EA: "copyright" (MetaQuotes Ltd., 2024), "link" (website), and "version" (1.00)—like a name tag. We use "#include <Trade/Trade.mqh>" to bring in the "CTrade" class, and create "obj_Trade" to handle trades. We set "mahandle" to "INVALID_HANDLE" for our "SMA". Inputs let you customize: "LotSize" (0.1) is trade size, "BreakoutOffsetPoints" (10) shifts entry, "BoxTimeframe" ("PERIOD_M15") is the chart, "ma_period" (50) sets the "SMA", "ma_method" ("MODE_SMA") and "ma_app_price" ("PRICE_CLOSE") define it, "risktoreward" (1.3) scales profits, "magicNumber" (12345) tags trades, and "isApplyTrailingStop" (false) toggles trailing stops. Session times—"sessionStartHour" (23), "sessionStartMin" (00), "sessionEndHour" (03), "sessionEndMin" (00)—define the Asian range, and "tradeExitHour" (13), "tradeExitMin" (00) set closure. Variables like "lastBoxSessionEnd", "boxCalculated", "ordersPlaced", "BoxHigh", and "BoxLow" track the range and state. In "OnInit", we set "obj_Trade"’s "magicNumber" with "SetExpertMagicNumber", create "mahandle" with "iMA" for a "50-period SMA", check if it fails (log with "Print", return "INIT_FAILED"), and return "INIT_SUCCEEDED" if ready.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   if (mahandle != INVALID_HANDLE){
      IndicatorRelease(mahandle);
   }
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   datetime currentTime = TimeCurrent();
   MqlDateTime dt;
   TimeToStruct(currentTime,dt);
   
   if (dt.hour > sessionEndHour || (dt.hour == sessionEndHour && dt.min >= sessionEndMin)){
      MqlDateTime sesEnd;
      sesEnd.year = dt.year;
      sesEnd.mon = dt.mon;
      sesEnd.day = dt.day;
      sesEnd.hour = sessionEndHour;
      sesEnd.min = sessionEndMin;
      sesEnd.sec = 0;
      
      datetime sessionEnd = StructToTime(sesEnd);
      
      datetime sessionStart;
      
      if (sessionStartHour > sessionEndHour || (sessionStartHour == sessionEndHour && sessionStartMin >= sessionEndMin)){
         datetime prevDay = sessionEnd - 86400;
         MqlDateTime dtPrev;
         TimeToStruct(prevDay, dtPrev);
         dtPrev.hour = sessionStartHour;
         dtPrev.min = sessionStartMin;
         dtPrev.sec = 0;
         sessionStart = StructToTime(dtPrev);
      }
      else{
         MqlDateTime temp;
         temp.year = sesEnd.year;
         temp.mon = sesEnd.mon;
         temp.day = sesEnd.day;
         temp.hour = sessionStartHour;
         temp.min = sessionStartMin;
         temp.sec = 0;
         sessionStart = StructToTime(temp);
      }
      if (sessionEnd != lastBoxSessionEnd){
         ComputeBox(sessionStart,sessionEnd);
         lastBoxSessionEnd = sessionEnd;
         boxCalculated = true;
         ordersPlaced = false;
      }
   }
   
   MqlDateTime exitTimeStruct;
   TimeToStruct(currentTime, exitTimeStruct);
   exitTimeStruct.hour = tradeExitHour;
   exitTimeStruct.min = tradeExitMin;
   exitTimeStruct.sec = 0;
   datetime tradeExitTime = StructToTime(exitTimeStruct);
   
   if (boxCalculated && !ordersPlaced && currentTime < tradeExitTime){
      double maBuffer[];
      if (CopyBuffer(mahandle,0,0,1,maBuffer) <= 0){
         Print("Failed to copy the MA Buffer.");
         return;
      }
      double maValue = maBuffer[0];
      
      double currentPrice = SymbolInfoDouble(_Symbol,SYMBOL_BID);
      bool bullish = (currentPrice > maValue);
      bool bearish = (currentPrice < maValue);
      
      double offsetPrice = BreakoutOffsetPoints *_Point;
      
      if (bullish){
         double entryPrice = BoxHigh + offsetPrice;
         double stoploss = BoxLow - offsetPrice;
         double risk = entryPrice - stoploss;
         double takeprofit = entryPrice + risk*risktoreward;
         if (obj_Trade.BuyStop(LotSize,entryPrice,_Symbol,stoploss,takeprofit,ORDER_TIME_GTC,0,"Asian Breakout Buystop Order")){
            Print("Placed the Buy stop order at ",entryPrice);
            ordersPlaced = true;
         }
         else{
            Print("Buy stop order failed: ",obj_Trade.ResultRetcodeDescription());
         }
      }
      else if (bearish){
         double entryPrice = BoxLow - offsetPrice;
         double stoploss = BoxHigh + offsetPrice;
         double risk = stoploss - entryPrice;
         double takeprofit = entryPrice - risk*risktoreward;
         if (obj_Trade.SellStop(LotSize,entryPrice,_Symbol,stoploss,takeprofit,ORDER_TIME_GTC,0,"Asian Breakout Sellstop Order")){
            Print("Placed the Sell stop order at ",entryPrice);
            ordersPlaced = true;
         }
         else{
            Print("Sell stop order failed: ",obj_Trade.ResultRetcodeDescription());
         }
      }
   }
   
   if (isApplyTrailingStop){
      if (PositionsTotal() > 0){
         applyTrailingStop(30*_Point,obj_Trade,magicNumber);
      }
   }
   
   if (currentTime >= tradeExitTime){
      CloseOpenPositions();
      CancelPendingOrders();
      boxCalculated = false;
      ordersPlaced = false;
   }
   
}

Now, we clean up and trade! The "OnDeinit" function runs when you remove the EA. If "mahandle" isn’t "INVALID_HANDLE", we free it with "IndicatorRelease" to avoid memory leaks—like tidying up after play. The "OnTick" function runs on each price tick, keeping the EA alive. We get the current time with "TimeCurrent" and break it into parts (year, hour, etc.) with "TimeToStruct" into "dt". If the time is past the session end (after "sessionEndHour":"sessionEndMin", 03:00), we build "sessionEnd" using "MqlDateTime" and "StructToTime". For "sessionStart", if "sessionStartHour" (23) is later than "sessionEndHour" (3), we subtract 86400 seconds (1 day) and set 23:00; otherwise, it’s the same day at 23:00. If "sessionEnd" is new (not "lastBoxSessionEnd"), we call "ComputeBox" to find the range, update "lastBoxSessionEnd", set "boxCalculated" to true, and reset "ordersPlaced". We set "tradeExitTime" to 13:00. If the box is ready, no orders are placed, and it’s before 13:00, we: get the "SMA" value with "CopyBuffer" into "maBuffer", check "currentPrice" ("SYMBOL_BID") against "maValue". If bullish (price > "SMA"), we set a buy stop at "BoxHigh" + "offsetPrice", stop-loss at "BoxLow" - "offsetPrice", and take-profit with "risktoreward"; if bearish, a sell stop below "BoxLow". We use "obj_Trade.BuyStop" or "SellStop", log results with "Print", and set "ordersPlaced". If "isApplyTrailingStop" is true and positions exist ("PositionsTotal"), we call "applyTrailingStop". At 13:00, we call "CloseOpenPositions" and "CancelPendingOrders", resetting flags.

void ComputeBox(datetime sessionstart, datetime sessionend){
   int totalBars = Bars(_Symbol,BoxTimeframe);
   if (totalBars <= 0){
      Print("NO BARS AVAILABLE ON TIMEFRAME ",EnumToString(BoxTimeframe));
      return;
   }
   
   MqlRates rates[];
   ArraySetAsSeries(rates,false);
   int copied = CopyRates(_Symbol,BoxTimeframe,0,totalBars,rates);
   if (copied <= 0){
      Print("Failed to copy the rates for box calculation.");
      return;
   }
   
   double highVal = -DBL_MAX;
   double lowVal = DBL_MAX;
   
   BoxHighTime = 0;
   BoxLowTime = 0;
   
   for (int i=0; i<copied; i++){
      if (rates[i].time >= sessionstart && rates[i].time <= sessionend){
         if (rates[i].high > highVal){
            highVal = rates[i].high;
            BoxHighTime = rates[i].time;
         }
         if (rates[i].low < lowVal){
            lowVal = rates[i].low;
            BoxLowTime = rates[i].time;
         }
      }
   }
   if (highVal == -DBL_MAX || lowVal == DBL_MAX){
      Print("No valid bars found within the session time range.");
      return;
   }
   
   BoxHigh = highVal;
   BoxLow = lowVal;
   
   Print("Session box computed: High = ",BoxHigh," at ",TimeToString(BoxHighTime),
      ", Low = ",BoxLow," at ",TimeToString(BoxLowTime)
   );
   DrawSessionObjects(sessionstart,sessionend);
}

void DrawSessionObjects(datetime sessionstart, datetime sessionend){
   
   int chartScale = (int)ChartGetInteger(0,CHART_SCALE,0);
   int dynamicLineWidth = (int)MathRound(1+(chartScale*2.0/5));
   int dynamicFontSize = 7 + chartScale*1;
   
   string sessionID = "Sess_"+IntegerToString(lastBoxSessionEnd);
   
   string rectName = "SessionRect_"+sessionID;
   if (!ObjectCreate(0,rectName,OBJ_RECTANGLE,0,BoxHighTime,BoxHigh,BoxLowTime,BoxLow))
      Print("Failed to create the rectangle: ",rectName);
   ObjectSetInteger(0,rectName,OBJPROP_COLOR,clrThistle);
   ObjectSetInteger(0,rectName,OBJPROP_FILL,true);
   ObjectSetInteger(0,rectName,OBJPROP_BACK,true);
   
   string topLineName = "SessionTopLine_"+sessionID;
   if (!ObjectCreate(0,topLineName,OBJ_TREND,0,sessionstart,BoxHigh,sessionend,BoxHigh))
      Print("Failed to create the top line: ",topLineName);
   ObjectSetInteger(0,topLineName,OBJPROP_COLOR,clrBlue);
   ObjectSetInteger(0,topLineName,OBJPROP_WIDTH,dynamicLineWidth);
   ObjectSetInteger(0,topLineName,OBJPROP_RAY_RIGHT,false);
   
   string botLineName = "SessionBottomLine_"+sessionID;
   if (!ObjectCreate(0,botLineName,OBJ_TREND,0,sessionstart,BoxLow,sessionend,BoxLow))
      Print("Failed to create the bottom line: ",botLineName);
   ObjectSetInteger(0,botLineName,OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,botLineName,OBJPROP_WIDTH,dynamicLineWidth);
   ObjectSetInteger(0,botLineName,OBJPROP_RAY_RIGHT,false);
   
   string topLabelName = "SessionTopLabel_"+sessionID;
   if (!ObjectCreate(0,topLabelName,OBJ_TEXT,0,sessionend,BoxHigh))
      Print("Failed to create the top label: ",topLabelName);
   ObjectSetString(0,topLabelName,OBJPROP_TEXT," "+DoubleToString(BoxHigh,_Digits));
   ObjectSetInteger(0,topLabelName,OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(0,topLabelName,OBJPROP_FONTSIZE,dynamicFontSize);
   ObjectSetInteger(0,topLabelName,OBJPROP_ANCHOR,ANCHOR_LEFT);
   
   string botLabelName = "SessionBottomLabel_"+sessionID;
   if (!ObjectCreate(0,botLabelName,OBJ_TEXT,0,sessionend,BoxLow))
      Print("Failed to create the bottom label: ",botLabelName);
   ObjectSetString(0,botLabelName,OBJPROP_TEXT," "+DoubleToString(BoxLow,_Digits));
   ObjectSetInteger(0,botLabelName,OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(0,botLabelName,OBJPROP_FONTSIZE,dynamicFontSize);
   ObjectSetInteger(0,botLabelName,OBJPROP_ANCHOR,ANCHOR_LEFT);
   
}

Next, we calculate and draw the range! The "ComputeBox" function finds the Asian session’s range. We count bars with "Bars" on "_Symbol" and "BoxTimeframe" (15-minute). If none, we "Print" and stop. We load price data into "rates" with "CopyRates", sorted oldest-first via "ArraySetAsSeries". We loop through bars between "sessionstart" (23:00) and "sessionend" (03:00), tracking the highest ("highVal") and lowest ("lowVal") prices, and their times ("BoxHighTime", "BoxLowTime"). If valid, we set "BoxHigh" and "BoxLow", log with "Print" and "TimeToString", then call "DrawSessionObjects". There, we get the chart zoom with "ChartGetInteger", adjust "dynamicLineWidth" and "dynamicFontSize", and create a unique "sessionID". We draw: an "OBJ_RECTANGLE" (thistle-colored, filled) for the range, "OBJ_TREND" lines for the top (blue) and bottom (red), and "OBJ_TEXT" labels for "BoxHigh" and "BoxLow" in black, sized dynamically, all via "ObjectCreate" and properties like "OBJPROP_COLOR". If any fail, we "Print" errors.

void CloseOpenPositions(){
   int totalPositions = PositionsTotal();
   for (int i=totalPositions-1; i>=0; i--){
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket)){
         if (PositionGetInteger(POSITION_MAGIC) == magicNumber){
            if (!obj_Trade.PositionClose(ticket))
               Print("Failed to close position ",ticket,": ",obj_Trade.ResultRetcodeDescription());
            else
               Print("Closed position ",ticket);
         }
      }
   }
}

void CancelPendingOrders(){
   int totalOrders = OrdersTotal();
   for (int i=totalOrders-1; i>=0; i--){
      ulong ticket = OrderGetTicket(i);
      if (OrderSelect(ticket)){
         int type = (int)OrderGetInteger(ORDER_TYPE);
         if (OrderGetInteger(ORDER_MAGIC) == magicNumber && 
            (type == ORDER_TYPE_BUY_STOP || type == ORDER_TYPE_SELL_STOP)
         ){
            if (!obj_Trade.OrderDelete(ticket))
               Print("Failed to cancel pending order ",ticket);
            else
               Print("Canceled pending order ",ticket);
         }
      }
   }
}

void applyTrailingStop(double slPoints, CTrade &trade_object,int magicNo =0){
   double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID)-slPoints,_Digits);
   double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK)+slPoints,_Digits);
   
   for (int i=PositionsTotal()-1; i>=0; i--){
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0){
         if (PositionGetString(POSITION_SYMBOL)==_Symbol &&
            (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)
         ){
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
               buySl > PositionGetDouble(POSITION_PRICE_OPEN) &&
               (buySl > PositionGetDouble(POSITION_SL) ||
               PositionGetDouble(POSITION_SL) == 0)
            ){
               trade_object.PositionModify(ticket,buySl,PositionGetDouble(POSITION_TP));
            }
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
               sellSl < PositionGetDouble(POSITION_PRICE_OPEN) &&
               (sellSl < PositionGetDouble(POSITION_SL) ||
               PositionGetDouble(POSITION_SL) == 0)
            ){
               trade_object.PositionModify(ticket,sellSl,PositionGetDouble(POSITION_TP));
            }
         }
      }
   }
   
}

To finish, we manage trades! The "CloseOpenPositions" function checks "PositionsTotal" for open trades. For each, we get the ticket with "PositionGetTicket", select it with "PositionSelectByTicket", and check if its "POSITION_MAGIC" matches "magicNumber". If so, we close it with "obj_Trade.PositionClose", logging success or failure with "Print" and "ResultRetcodeDescription". The "CancelPendingOrders" function checks "OrdersTotal" for pending orders. For each, we get the ticket with "OrderGetTicket", select it with "OrderSelect", and verify "ORDER_MAGIC" and type ("ORDER_TYPE_BUY_STOP" or "ORDER_TYPE_SELL_STOP"). If valid, we cancel with "obj_Trade.OrderDelete", logging the result. The "applyTrailingStop" function adjusts stop-losses: we set "buySl" below the "SYMBOL_BID" and "sellSl" above the "SYMBOL_ASK" by 30 points, normalized with "NormalizeDouble". For each position, if it’s our symbol and "magicNumber", we check the type: for buys, if "buySl" is above entry and better than the current stop, we update with "PositionModify"; for sells, if "sellSl" is below and better, we adjust. This follows price to lock in gains!

Conclusion

The "Asian Session Breakout EA" is a clear, automated strategy for new traders. We set up inputs, calculate the Asian range, place breakout orders with "SMA" filters, draw visuals, and manage trades. Test it in MetaTrader 5!

Disclaimer: For education only. Trading is risky—test on a demo first.

Happy trading!

Disclaimer: The ideas and strategies presented in this resource are solely those of the author and are intended for informational and educational purposes only. They do not constitute financial advice, and past performance is not indicative of future results. All materials, including but not limited to text, images, files, and any downloadable content, are protected by copyright and intellectual property laws and are the exclusive property of Forex Algo-Trader or its licensors. Reproduction, distribution, modification, or commercial use of these materials without prior written consent from Forex Algo-Trader is strictly prohibited and may result in legal action. Users are advised to exercise extreme caution, perform thorough independent research, and consult with qualified financial professionals before implementing any trading strategies or decisions based on this resource, as trading in financial markets involves significant risk of loss.

Recent Comments

Go to discussion to Comment or View other Comments

No comments yet. Be the first to comment!