preview
Automating Trading Strategies in MQL5 (Part 24): London Session Breakout System with Risk Management and Trailing Stops

Automating Trading Strategies in MQL5 (Part 24): London Session Breakout System with Risk Management and Trailing Stops

MetaTrader 5Trading |
649 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 23), we enhanced the Zone Recovery System for Envelopes Trend Trading in MetaQuotes Language 5 (MQL5) with trailing stops and multi-basket trading for better profit protection and signal handling. In Part 24, we develop a London Session Breakout System that identifies pre-session ranges, places pending orders, and incorporates risk management tools, including risk-to-reward ratios, drawdown limits, and a control panel for real-time monitoring. We will cover the following topics:

  1. Understanding the London Session Breakout Strategy
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have a complete MQL5 breakout program with advanced risk controls, ready for testing and refinement—let’s dive in!


Understanding the London Session Breakout Strategy

The London Session Breakout Strategy targets the increased volatility during the London market open by identifying the price range formed in the pre-London hours and placing pending orders to capture breakouts from that range. This strategy is important because the London session often experiences high liquidity and price movements, offering reliable opportunities for profit; however, it requires careful risk management to avoid false breakouts and drawdowns.

We will achieve this by calculating the pre-London high and low to set buy and sell stop orders with offsets, incorporating risk-to-reward ratios for take-profits, trailing stops for profit locking, and limits on open trades and daily drawdown to protect capital. We plan to use a control panel for real-time monitoring and session-specific checks to ensure trades only occur within defined ranges, making the system adaptable to varying market conditions. In a nutshell, here is a representation of the system we want to achieve.

STRATEGY BLUEPRINT


Implementation in MQL5

To create the program in MQL5, open the MetaEditor, go to the Navigator, locate the Indicators folder, click on the "New" tab, and follow the prompts to create the file. Once it is made, in the coding environment, we will start by declaring some inputs and structures that will make the program more dynamic.

//+------------------------------------------------------------------+
//|                                        London Breakout EA.mq5    |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh> //--- Include Trade library for trading operations

//--- Enumerations
enum ENUM_TRADE_TYPE {     //--- Enumeration for trade types
   TRADE_ALL,              // All Trades (Buy and Sell)
   TRADE_BUY_ONLY,         // Buy Trades Only
   TRADE_SELL_ONLY         // Sell Trades Only
};

//--- Input parameters
sinput group "General EA Settings"
input double inpTradeLotsize = 0.01; // Lotsize
input ENUM_TRADE_TYPE TradeType = TRADE_ALL; // Trade Type Selection
sinput int MagicNumber = 12345;     // Magic Number
input double RRRatio = 1.0;        // Risk to Reward Ratio
input int StopLossPoints = 500;    // Stop loss in points
input int OrderOffsetPoints = 10;   // Points offset for Orders
input bool DeleteOppositeOrder = true; // Delete opposite order when one is activated?
input bool UseTrailing = false;    // Use Trailing Stop?
input int TrailingPoints = 50;     // Trailing Points (distance)
input int MinProfitPoints = 100;   // Minimum Profit Points to start trailing

sinput group "London Session Settings"
input int LondonStartHour = 9;        // London Start Hour
input int LondonStartMinute = 0;      // London Start Minute
input int LondonEndHour = 8;          // London End Hour
input int LondonEndMinute = 0;        // London End Minute
input int MinRangePoints = 100;       // Min Pre-London Range in points
input int MaxRangePoints = 300;       // Max Pre-London Range in points

sinput group "Risk Management"
input int MaxOpenTrades = 2;       // Maximum simultaneous open trades
input double MaxDailyDrawdownPercent = 5.0; // Max daily drawdown % to stop trading

//--- Structures
struct PositionInfo {      //--- Structure for position information
   ulong ticket;           // Position ticket
   double openPrice;      // Entry price
   double londonRange;    // Pre-London range in points for this position
   datetime sessionID;    // Session identifier (day)
   bool trailingActive;   // Trailing active flag
};

We begin implementing our London Session Breakout System by including the "<Trade\Trade.mqh>" library and defining key enumerations, inputs, and a structure for position tracking. We include "<Trade\Trade.mqh>" to access the CTrade class for executing trading operations like placing orders and modifying positions. We define the "ENUM_TRADE_TYPE" enumeration with options "TRADE_ALL" for both buy and sell trades, "TRADE_BUY_ONLY" for buys only, and "TRADE_SELL_ONLY" for sells only, allowing us to restrict trade directions.

We then set up input parameters in groups: under "General EA Settings", "inpTradeLotsize" at 0.01 for lot size, "TradeType" using the enumeration with default "TRADE_ALL", "MagicNumber" at 12345 to identify EA trades, "RRRatio" at 1.0 for risk-reward ratio, "StopLossPoints" at 500 for stop-loss distance, "OrderOffsetPoints" at 10 for entry offsets, "DeleteOppositeOrder" as true to remove opposite pending orders, "UseTrailing" as false to enable trailing stops, "TrailingPoints" at 50 for trailing distance, and "MinProfitPoints" at 100 to start trailing.

Under "London Session Settings", "LondonStartHour" at 9 and "LondonStartMinute" at 0 for session start, "LondonEndHour" at 8 and "LondonEndMinute" at 0 for end, "MinRangePoints" at 100 and "MaxRangePoints" at 300 for pre-London range validation. Under "Risk Management", "MaxOpenTrades" is set at 2 to limit simultaneous positions, and "MaxDailyDrawdownPercent" at 5.0 to halt trading on excessive drawdown. We define the "PositionInfo" structure to track open trades, with "ticket" for the position ticket, "openPrice" for entry price, "londonRange" for the pre-London range, "sessionID" for the day identifier, and "trailingActive" as a boolean for trailing status. Upon compilation, we have the following output.

INPUT SET

With the inputs set in that structured manner, we can now define some extra global variables that we will use throughout the program.

//--- Global variables
CTrade obj_Trade;                 //--- Trade object
double PreLondonHigh = 0.0;       //--- Pre-London session high
double PreLondonLow = 0.0;        //--- Pre-London session low
datetime PreLondonHighTime = 0;   //--- Time of Pre-London high
datetime PreLondonLowTime = 0;    //--- Time of Pre-London low
ulong buyOrderTicket = 0;         //--- Buy stop order ticket
ulong sellOrderTicket = 0;        //--- Sell stop order ticket
bool panelVisible = true;         //--- Panel visibility flag
double LondonRangePoints = 0.0;   //--- Current session's Pre-London range
PositionInfo positionList[];      //--- Array to store position info
datetime lastCheckedDay = 0;      //--- Last checked day
bool noTradeToday = false;        //--- Flag to prevent trading today
bool sessionChecksDone = false;   //--- Flag for session checks completion
datetime analysisTime = 0;        //--- Time for London analysis
double dailyDrawdown = 0.0;       //--- Current daily drawdown
bool isTrailing = false;          //--- Global flag for any trailing active
const int PreLondonStartHour = 3; //--- Fixed Pre-London Start Hour
const int PreLondonStartMinute = 0; //--- Fixed Pre-London Start Minute

Here, we define global variables for our program: "obj_Trade" as CTrade for trading, "PreLondonHigh" and "PreLondonLow" as doubles for ranges, "PreLondonHighTime" and "PreLondonLowTime" as datetimes for timings, "buyOrderTicket" and "sellOrderTicket" as ulongs for orders, "panelVisible" as true for the panel, "LondonRangePoints" as 0.0 for current range, "positionList" as "PositionInfo" array for positions, "lastCheckedDay" as 0 for daily tracking, "noTradeToday" and "sessionChecksDone" as false for trading flags, "analysisTime" as 0 for session timing, "dailyDrawdown" as 0.0 for risk, "isTrailing" as false for trailing, and constants "PreLondonStartHour" as 3 and "PreLondonStartMinute" as 0.

With that done, we will proceed to create the panel, which is the easiest step, then advance to the more complex trading logic. Let's start with the necessary creation functions.

//+------------------------------------------------------------------+
//| Create a rectangle label for the panel background                |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xD, int yD, int xS, int yS,
                    color clrBg, int widthBorder, color clrBorder = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) {
    ResetLastError();              //--- Reset last error
    if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label
        Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log creation failure
        return false;              //--- Return failure
    }
    ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance
    ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance
    ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set x-size
    ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set y-size
    ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
    ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color
    ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
    ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style
    ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width
    ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color
    ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground
    ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state
    ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable
    ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected
    ChartRedraw(0);                //--- Redraw chart
    return true;                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create a text label for panel elements                           |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xD, int yD,
                 string txt, color clrTxt = clrBlack, int fontSize = 10,
                 string font = "Arial") {
    ResetLastError();              //--- Reset last error
    if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label
        Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log creation failure
        return false;              //--- Return failure
    }
    ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance
    ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance
    ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
    ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text
    ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color
    ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
    ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font
    ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground
    ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state
    ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable
    ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected
    ChartRedraw(0);                //--- Redraw chart
    return true;                   //--- Return success
}

Here, we implement utility functions to create the control panel User Interface (UI) elements using rectangle labels for backgrounds and text labels for display. We start with the "createRecLabel" function to generate rectangle labels for panel backgrounds, taking some parameters. We reset errors with the ResetLastError function and create the object with ObjectCreate as OBJ_RECTANGLE_LABEL, logging failures with Print, and returning false if unsuccessful. We set properties using ObjectSetInteger for OBJPROP_XDISTANCE and the same case to all the other necessary integer properties, then redraw the chart with ChartRedraw and return true.

Next, we create the "createLabel" function for text labels in the panel, taking "objName", "xD", "yD", "txt", "clrTxt", "fontSize", and "font" as parameters. We reset errors with "ResetLastError" and create the object with "ObjectCreate" as OBJ_LABEL, logging failures with "Print", and returning false if unsuccessful. We set properties using the "ObjectSetInteger" function just as we did with the rectangle label function, but here, we use the additional ObjectSetString function for "OBJPROP_TEXT" and OBJPROP_FONT properties, then redraw and return true. These functions will enable us to build a dynamic control panel for monitoring session data and program status. We can now use the functions to create and update the panel.

string panelPrefix = "LondonPanel_"; //--- Prefix for panel objects

//+------------------------------------------------------------------+
//| Create the information panel                                     |
//+------------------------------------------------------------------+
void CreatePanel() {
   createRecLabel(panelPrefix + "Background", 10, 10, 270, 200, clrMidnightBlue, 1, clrSilver); //--- Create background
   createLabel(panelPrefix + "Title", 20, 15, "London Breakout Control Center", clrGold, 12); //--- Create title
   createLabel(panelPrefix + "RangePoints", 20, 40, "Range (points): ", clrWhite, 10); //--- Create range label
   createLabel(panelPrefix + "HighPrice", 20, 60, "High Price: ", clrWhite); //--- Create high price label
   createLabel(panelPrefix + "LowPrice", 20, 80, "Low Price: ", clrWhite); //--- Create low price label
   createLabel(panelPrefix + "BuyLevel", 20, 100, "Buy Level: ", clrWhite); //--- Create buy level label
   createLabel(panelPrefix + "SellLevel", 20, 120, "Sell Level: ", clrWhite); //--- Create sell level label
   createLabel(panelPrefix + "AccountBalance", 20, 140, "Balance: ", clrWhite); //--- Create balance label
   createLabel(panelPrefix + "AccountEquity", 20, 160, "Equity: ", clrWhite); //--- Create equity label
   createLabel(panelPrefix + "CurrentDrawdown", 20, 180, "Drawdown (%): ", clrWhite); //--- Create drawdown label
   createRecLabel(panelPrefix + "Hide", 250, 10, 30, 22, clrCrimson, 1, clrNONE); //--- Create hide button
   createLabel(panelPrefix + "HideText", 258, 12, CharToString(251), clrWhite, 13, "Wingdings"); //--- Create hide text
   ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_SELECTABLE, true); //--- Make hide selectable
   ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_STATE, true); //--- Set hide state
}

//+------------------------------------------------------------------+
//| Update panel with current data                                   |
//+------------------------------------------------------------------+
void UpdatePanel() {
   string rangeText = "Range (points): " + (LondonRangePoints > 0 ? DoubleToString(LondonRangePoints, 0) : "Calculating..."); //--- Format range text
   ObjectSetString(0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); //--- Update range text
   
   string highText = "High Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh, _Digits) : "N/A"); //--- Format high text
   ObjectSetString(0, panelPrefix + "HighPrice", OBJPROP_TEXT, highText); //--- Update high text
   
   string lowText = "Low Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow, _Digits) : "N/A"); //--- Format low text
   ObjectSetString(0, panelPrefix + "LowPrice", OBJPROP_TEXT, lowText); //--- Update low text
   
   string buyText = "Buy Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh + OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format buy text
   ObjectSetString(0, panelPrefix + "BuyLevel", OBJPROP_TEXT, buyText); //--- Update buy text
   
   string sellText = "Sell Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow - OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format sell text
   ObjectSetString(0, panelPrefix + "SellLevel", OBJPROP_TEXT, sellText); //--- Update sell text
   
   string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Format balance text
   ObjectSetString(0, panelPrefix + "AccountBalance", OBJPROP_TEXT, balanceText); //--- Update balance text
   
   string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Format equity text
   ObjectSetString(0, panelPrefix + "AccountEquity", OBJPROP_TEXT, equityText); //--- Update equity text
   
   string ddText = "Drawdown (%): " + DoubleToString(dailyDrawdown, 2); //--- Format drawdown text
   ObjectSetString(0, panelPrefix + "CurrentDrawdown", OBJPROP_TEXT, ddText); //--- Update drawdown text
   ObjectSetInteger(0, panelPrefix + "CurrentDrawdown", OBJPROP_COLOR, dailyDrawdown > MaxDailyDrawdownPercent / 2 ? clrYellow : clrWhite); //--- Set drawdown color
}

Here, we define the "panelPrefix" string as "LondonPanel_" to prefix all panel object names, ensuring organized identification for the control panel. We create the "CreatePanel" function to build the information panel UI. We call "createRecLabel" for "panelPrefix + "Background" to make the panel background at position 10,10 with size 270x200, "clrMidnightBlue" background, width 1, and a silver border. We use "createLabel" to add the title "London Breakout Control Center" at 20,15 with gold color and size 12, and labels for range, high price, low price, buy level, sell level, balance, equity, and drawdown at respective positions with white color and size 10.

For the hide button, we call "createRecLabel" for "panelPrefix + 'Hide' at 250,10 with size 30x22 and "clrCrimson" background, and "createLabel" for "panelPrefix + 'HideText' with CharToString(251) from Wingdings at 258,12 with "clrWhite" color and size 13. We set OBJPROP_SELECTABLE and "OBJPROP_STATE" to true for the hide button with ObjectSetInteger to make it interactive. The choice of the Wingdings code to use is dependent on your aesthetic feel. Here is a list of code you can choose from.

WINGDINGS CODES

We then implement the "UpdatePanel" function to refresh the panel with the current data. We format "rangeText" with "LondonRangePoints" using DoubleToString or "Calculating..." if zero, and update "panelPrefix + "RangePoints" text with the ObjectSetString function. We similarly format and update texts for high price, low price, buy level (adding "OrderOffsetPoints * _Point" to "PreLondonHigh"), sell level (subtracting "OrderOffsetPoints * _Point" from "PreLondonLow"), balance with AccountInfoDouble(ACCOUNT_BALANCE), equity with "AccountInfoDouble(ACCOUNT_EQUITY)", and drawdown with "dailyDrawdown".

We set the drawdown color with ObjectSetInteger to yellow if daily drawdown exceeds "MaxDailyDrawdownPercent / 2", else white. To make the functions usable, we call them in the initialization function as follows.

//+------------------------------------------------------------------+
//| Initialize EA                                                    |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number
   ArrayFree(positionList);           //--- Free position list
   CreatePanel();                     //--- Create panel
   panelVisible = true;               //--- Set panel visible
   return(INIT_SUCCEEDED);            //--- Return success
}

//+------------------------------------------------------------------+
//| Deinitialize EA                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects
   ArrayFree(positionList);           //--- Free position list
}

On the OnInit event handler, we set the magic number using the trade object, use the ArrayFree function to free the position list, call the "CreatePanel" function to create the panel, and set the panel visibility flag to true after its creation. Then, on the OnDeinit event handler, we use the ObjectsDeleteAll function to delete all the objects with the specified prefix and free the position list array since we no longer need it. When we compile, we get the following outcome.

INITIAL PANEL

Since we have created the panel, let us now add some life to it by making the cancel button responsive, where we will destroy the panel once it is clicked. We will achieve this in the OnChartEvent event handler.

//+------------------------------------------------------------------+
//| Handle chart events (e.g., panel close)                          |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_OBJECT_CLICK && sparam == panelPrefix + "Hide") { //--- Check hide click
      panelVisible = false;           //--- Set panel hidden
      ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects
      ChartRedraw(0);                 //--- Redraw chart
   }
}

In the OnChartEvent event handler, we check if the event ID is an object click and the object is the hide or cancel button, and then set the visibility flag to false. We then delete the panel objects and redraw the chart for changes to take effect. Upon compilation, we get the following outcome.

PANEL CLOSE

From the visualization, we can see that the panel is now all set. Let us update it so that it initializes all parts. We will need some functions to set the daily ranges and to calculate the drawdown features.

//+------------------------------------------------------------------+
//| Check if it's a new trading day                                  |
//+------------------------------------------------------------------+
bool IsNewDay(datetime currentBarTime) {
   MqlDateTime barTime;               //--- Bar time structure
   TimeToStruct(currentBarTime, barTime); //--- Convert time
   datetime currentDay = StringToTime(StringFormat("%04d.%02d.%02d", barTime.year, barTime.mon, barTime.day)); //--- Get current day
   if (currentDay != lastCheckedDay) { //--- Check new day
      lastCheckedDay = currentDay;    //--- Update last day
      sessionChecksDone = false;      //--- Reset checks
      noTradeToday = false;           //--- Reset no trade
      buyOrderTicket = 0;             //--- Reset buy ticket
      sellOrderTicket = 0;            //--- Reset sell ticket
      LondonRangePoints = 0.0;        //--- Reset range
      return true;                    //--- Return new day
   }
   return false;                      //--- Return not new day
}

//+------------------------------------------------------------------+
//| Update daily drawdown                                            |
//+------------------------------------------------------------------+
void UpdateDailyDrawdown() {
   static double maxEquity = 0.0;     //--- Max equity tracker
   double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity
   if (equity > maxEquity) maxEquity = equity; //--- Update max equity
   dailyDrawdown = (maxEquity - equity) / maxEquity * 100; //--- Calculate drawdown
   if (dailyDrawdown >= MaxDailyDrawdownPercent) noTradeToday = true; //--- Set no trade if exceeded
}

Here, we implement the "IsNewDay" function to check for a new trading day. We create a MqlDateTime structure "barTime" and convert "currentBarTime" to it with the TimeToStruct function. We calculate "currentDay" using StringToTime and StringFormat from "barTime.year", "barTime.mon", and "barTime.day". If "currentDay" differs from "lastCheckedDay", we update "lastCheckedDay", reset "sessionChecksDone" and "noTradeToday" to false, clear "buyOrderTicket" and "sellOrderTicket" to 0, set "LondonRangePoints" to 0.0, and return true; otherwise, return false. This function ensures daily resets for session analysis and trading flags.

Next, we implement the "UpdateDailyDrawdown" function to monitor daily risk. We use a static "maxEquity" initialized to 0.0 to track peak equity. We get "equity" with AccountInfoDouble using "ACCOUNT_EQUITY", update "maxEquity" if "equity" is higher, and calculate "dailyDrawdown" as the percentage drop from "maxEquity". If "dailyDrawdown" meets or exceeds "MaxDailyDrawdownPercent", we set "noTradeToday" to true to halt trading, providing essential drawdown protection. To make the functions usable, we call them in the OnTick event handler to populate the panel with updated data.

//+------------------------------------------------------------------+
//| Main tick handler                                                |
//+------------------------------------------------------------------+
void OnTick() {
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
   IsNewDay(currentBarTime);          //--- Check new day
   
   UpdatePanel();                     //--- Update panel
   UpdateDailyDrawdown();             //--- Update drawdown
}

Here, we just call the new day function to set the range parameters, update the panel, and daily drawdown levels, respectively. When we run the program now, we have the following outcome.

INITIALIZED PANEL

From the visualization, we can see the panel is now populated with recent data and shows the status. Let us now move on to the more complex logic of defining the session ranges. Let's first check the trading conditions and place the orders if the trading conditions are met, and then manage the positions later. So we will need some functions to first let us define the range, visualize the ranges, and then place the orders. Here is the logic we use to achieve that.

//+------------------------------------------------------------------+
//| Fixed lot size                                                   |
//+------------------------------------------------------------------+
double CalculateLotSize(double entryPrice, double stopLossPrice) {
   return NormalizeDouble(inpTradeLotsize, 2); //--- Normalize lot size
}

//+------------------------------------------------------------------+
//| Calculate session range (high-low) in points                     |
//+------------------------------------------------------------------+
double GetRange(datetime startTime, datetime endTime, double &highVal, double &lowVal, datetime &highTime, datetime &lowTime) {
   int startBar = iBarShift(_Symbol, _Period, startTime, true); //--- Get start bar
   int endBar = iBarShift(_Symbol, _Period, endTime, true); //--- Get end bar
   if (startBar == -1 || endBar == -1 || startBar < endBar) return -1; //--- Invalid bars

   int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + 1, endBar); //--- Get highest bar
   int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + 1, endBar); //--- Get lowest bar
   highVal = iHigh(_Symbol, _Period, highestBar); //--- Set high value
   lowVal = iLow(_Symbol, _Period, lowestBar); //--- Set low value
   highTime = iTime(_Symbol, _Period, highestBar); //--- Set high time
   lowTime = iTime(_Symbol, _Period, lowestBar); //--- Set low time
   return (highVal - lowVal) / _Point; //--- Return range in points
}

//+------------------------------------------------------------------+
//| Place pending buy/sell stop orders                               |
//+------------------------------------------------------------------+
void PlacePendingOrders(double preLondonHigh, double preLondonLow, datetime sessionID) {
   double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; //--- Calculate buy price
   double sellPrice = preLondonLow - OrderOffsetPoints * _Point; //--- Calculate sell price
   double slPoints = StopLossPoints; //--- Set SL points
   double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL
   double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL
   double tpPoints = slPoints * RRRatio; //--- Calculate TP points
   double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP
   double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP
   double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot
   double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot

   if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { //--- Check buy trade
      obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop
      buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket
   }

   if (TradeType == TRADE_ALL || TradeType == TRADE_SELL_ONLY) { //--- Check sell trade
      obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop
      sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket
   }
}

//+------------------------------------------------------------------+
//| Draw session ranges on the chart                                 |
//+------------------------------------------------------------------+
void DrawSessionRanges(datetime preLondonStart, datetime londonEnd) {
   string sessionID = "Sess_" + IntegerToString(lastCheckedDay); //--- Session ID

   string preRectName = "PreRect_" + sessionID; //--- Rectangle name
   ObjectCreate(0, preRectName, OBJ_RECTANGLE, 0, PreLondonHighTime, PreLondonHigh, PreLondonLowTime, PreLondonLow); //--- Create rectangle
   ObjectSetInteger(0, preRectName, OBJPROP_COLOR, clrTeal); //--- Set color
   ObjectSetInteger(0, preRectName, OBJPROP_FILL, true); //--- Enable fill
   ObjectSetInteger(0, preRectName, OBJPROP_BACK, true); //--- Set background

   string preTopLineName = "PreTopLine_" + sessionID; //--- Top line name
   ObjectCreate(0, preTopLineName, OBJ_TREND, 0, preLondonStart, PreLondonHigh, londonEnd, PreLondonHigh); //--- Create top line
   ObjectSetInteger(0, preTopLineName, OBJPROP_COLOR, clrBlack); //--- Set color
   ObjectSetInteger(0, preTopLineName, OBJPROP_WIDTH, 1); //--- Set width
   ObjectSetInteger(0, preTopLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray
   ObjectSetInteger(0, preTopLineName, OBJPROP_BACK, true); //--- Set background

   string preBotLineName = "PreBottomLine_" + sessionID; //--- Bottom line name
   ObjectCreate(0, preBotLineName, OBJ_TREND, 0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); //--- Create bottom line
   ObjectSetInteger(0, preBotLineName, OBJPROP_COLOR, clrRed); //--- Set color
   ObjectSetInteger(0, preBotLineName, OBJPROP_WIDTH, 1); //--- Set width
   ObjectSetInteger(0, preBotLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray
   ObjectSetInteger(0, preBotLineName, OBJPROP_BACK, true); //--- Set background
}

To enable range calculation, order placement, and chart drawing, we start with the "CalculateLotSize" function to compute fixed lot sizes, taking "entryPrice" and "stopLossPrice" as parameters (unused for fixed sizing). We return "inpTradeLotsize" normalized to 2 decimals with NormalizeDouble, ensuring consistent lot sizes for all trades. You can use another decimal depending on your account type.

Next, we create the "GetRange" function to calculate the pre-London session range. We get "startBar" and "endBar" with the iBarShift function using "startTime" and "endTime", returning -1 if invalid or "startBar" < "endBar". We find "highestBar" with iHighest on MODE_HIGH and "lowestBar" with "iLowest" on MODE_LOW over the bar range. We set "highVal" with "iHigh" on "highestBar", "lowVal" with "iLow" on "lowestBar", "highTime" with "iTime" on "highestBar", and "lowTime" with "iTime" on "lowestBar". We return the range as the "(highVal-lowVal) /_Point" value.

We then define the "PlacePendingOrders" function to set up buy and sell stops. We calculate "buyPrice" as "preLondonHigh + OrderOffsetPoints * _Point" and "sellPrice" as "preLondonLow - OrderOffsetPoints * _Point". We set "slPoints" to "StopLossPoints", "buySL" as "buyPrice - slPoints * _Point", "sellSL" as "sellPrice + slPoints * _Point", "tpPoints" as "slPoints * RRRatio", "buyTP" as "buyPrice + tpPoints * _Point", and "sellTP" as "sellPrice - tpPoints * _Point". We compute "lotSizeBuy" and "lotSizeSell" with "CalculateLotSize".

If "TradeType" is "TRADE_ALL" or "TRADE_BUY_ONLY", we place a buy stop with "obj_Trade.BuyStop" using "lotSizeBuy", "buyPrice", "buySL", "buyTP", and label "Buy Stop - London", storing the ticket in "buyOrderTicket" from "ResultOrder". Similarly, for sell if "TRADE_ALL" or "TRADE_SELL_ONLY".

Finally, we implement the "DrawSessionRanges" function to visualize the session on the chart. We create "sessionID" as "Sess_" plus "lastCheckedDay" with the IntegerToString function. For the rectangle "preRectName" as "PreRect_" plus "sessionID", we use ObjectCreate as OBJ_RECTANGLE from "PreLondonHighTime", "PreLondonHigh" to "PreLondonLowTime", "PreLondonLow", setting OBJPROP_COLOR to "clrTeal", "OBJPROP_FILL" to true, and "OBJPROP_BACK" to true.

For the top line "preTopLineName" as "PreTopLine_" plus "sessionID", we create OBJ_TREND from "preLondonStart", "PreLondonHigh" to "londonEnd", "PreLondonHigh", setting "OBJPROP_COLOR" to "clrBlack", "OBJPROP_WIDTH" to 1, "OBJPROP_RAY_RIGHT" to false, and "OBJPROP_BACK" to true. Similarly, for the bottom line "preBotLineName" as "PreBottomLine_" plus "sessionID" from "preLondonStart", "PreLondonLow" to "londonEnd", "PreLondonLow", with red color. We can now define a function to check for the trading conditions using these functions.

//+------------------------------------------------------------------+
//| Check trading conditions and place orders                        |
//+------------------------------------------------------------------+
void CheckTradingConditions(datetime currentTime) {
   MqlDateTime timeStruct;            //--- Time structure
   TimeToStruct(currentTime, timeStruct); //--- Convert time
   datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); //--- Get today

   datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; //--- Pre-London start
   datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; //--- London start
   datetime londonEnd = today + LondonEndHour * 3600 + LondonEndMinute * 60; //--- London end
   analysisTime = londonStart;        //--- Set analysis time

   if (currentTime < analysisTime) return; //--- Exit if before analysis

   double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); //--- Get range
   if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { //--- Check range limits
      noTradeToday = true;            //--- Set no trade
      sessionChecksDone = true;       //--- Set checks done
      DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges
      return;                         //--- Exit
   }

   LondonRangePoints = preLondonRange; //--- Set range points
   PlacePendingOrders(PreLondonHigh, PreLondonLow, today); //--- Place orders
   noTradeToday = true;               //--- Set no trade
   sessionChecksDone = true;          //--- Set checks done
   DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges
}

We implement the "CheckTradingConditions" function to evaluate session conditions and place orders in our London Session Breakout System. We create a MqlDateTime structure, "timeStruct," and convert the current time to it with the TimeToStruct function. We calculate "today" using "StringToTime" and "StringFormat" from "timeStruct.year", "timeStruct.mon", and "timeStruct.day". We set "preLondonStart" as "today" plus "PreLondonStartHour" and "PreLondonStartMinute" in seconds, "londonStart" as "today" plus "LondonStartHour" and "LondonStartMinute", and "londonEnd" as "today" plus "LondonEndHour" and "LondonEndMinute". We assign "analysisTime" to "londonStart" and exit if the current time is before it.

We get "preLondonRange" with the "GetRange" function, passing "preLondonStart", "currentTime", and references for "PreLondonHigh", "PreLondonLow", "PreLondonHighTime", and "PreLondonLowTime". If "preLondonRange" is below "MinRangePoints" or above "MaxRangePoints", we set "noTradeToday" and "sessionChecksDone" to true, call "DrawSessionRanges" with "preLondonStart" and "londonEnd", and exit. Otherwise, we set "LondonRangePoints" to "preLondonRange", call "PlacePendingOrders" with "PreLondonHigh", "PreLondonLow", and "today", set "noTradeToday" and "sessionChecksDone" to true, and call "DrawSessionRanges", ensuring trades occur only within valid session ranges. We can call the function now in the OnTick event handler for the signals.

if (!noTradeToday && !sessionChecksDone) { //--- Check trading conditions
   CheckTradingConditions(TimeCurrent()); //--- Check conditions
}

If there are no trades done for today and no session checks done already, we call the function to check for the conditions for the current time. When we run the program, we get the following outcome.

RANGES SET

We can see that we set the ranges and placed the pending orders. The range is 100 points, which meets our trading conditions. We can now move on to managing the trading trades, but first, let us check and delete the pending orders when one is activated, and add the positions to the position list for management.

//+------------------------------------------------------------------+
//| Delete opposite pending order when one is filled                 |
//+------------------------------------------------------------------+
void CheckAndDeleteOppositeOrder() {
   if (!DeleteOppositeOrder || TradeType != TRADE_ALL) return; //--- Exit if not applicable

   bool buyOrderExists = false;       //--- Buy exists flag
   bool sellOrderExists = false;      //--- Sell exists flag

   for (int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders
      ulong orderTicket = OrderGetTicket(i); //--- Get ticket
      if (OrderSelect(orderTicket)) { //--- Select order
         if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Check symbol and magic
            if (orderTicket == buyOrderTicket) buyOrderExists = true; //--- Set buy exists
            if (orderTicket == sellOrderTicket) sellOrderExists = true; //--- Set sell exists
         }
      }
   }

   if (!buyOrderExists && sellOrderExists && sellOrderTicket != 0) { //--- Check delete sell
      obj_Trade.OrderDelete(sellOrderTicket); //--- Delete sell order
   } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != 0) { //--- Check delete buy
      obj_Trade.OrderDelete(buyOrderTicket); //--- Delete buy order
   }
}

//+------------------------------------------------------------------+
//| Add position to tracking list when opened                        |
//+------------------------------------------------------------------+
void AddPositionToList(ulong ticket, double openPrice, double londonRange, datetime sessionID) {
   if (londonRange <= 0) return;      //--- Exit if invalid range
   int index = ArraySize(positionList); //--- Get current size
   ArrayResize(positionList, index + 1); //--- Resize array
   positionList[index].ticket = ticket; //--- Set ticket
   positionList[index].openPrice = openPrice; //--- Set open price
   positionList[index].londonRange = londonRange; //--- Set range
   positionList[index].sessionID = sessionID; //--- Set session ID
   positionList[index].trailingActive = false; //--- Set trailing inactive
}

We start with the "CheckAndDeleteOppositeOrder" function to remove the opposite pending order when one is triggered. We exit early if "DeleteOppositeOrder" is false or "TradeType" is not "TRADE_ALL". We initialize "buyOrderExists" and "sellOrderExists" as false. We loop backward through orders with OrdersTotal and OrderGetTicket, selecting each with "OrderSelect". If the order matches "_Symbol" and "MagicNumber" via "OrderGetString" and OrderGetInteger, we set "buyOrderExists" or "sellOrderExists" if the ticket matches "buyOrderTicket" or "sellOrderTicket".

If the buy order is gone and the sell order exists, we delete "sellOrderTicket" with "obj_Trade.OrderDelete"; similarly, if the sell order is gone and the buy order exists. This function will ensure only one direction of trade after activation.

Next, we create the "AddPositionToList" function to track opened positions. We exit if "londonRange" is <= 0 because the range is not set yet. We get the current "index" with ArraySize of "positionList", resize it with ArrayResize to add one slot, and set "positionList[index]. ticket", "openPrice", "londonRange", "sessionID", and "trailingActive" to false, helping to maintain a list for managing trailing stops and session-specific data. We can now implement this logic on the tick event handler.

CheckAndDeleteOppositeOrder();     //--- Delete opposite order

// Add untracked positions
for (int i = 0; i < PositionsTotal(); i++) { //--- Iterate through positions
   ulong ticket = PositionGetTicket(i); //--- Get ticket
   if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check position
      bool tracked = false;        //--- Tracked flag
      for (int j = 0; j < ArraySize(positionList); j++) { //--- Check list
         if (positionList[j].ticket == ticket) tracked = true; //--- Set tracked
      }
      if (!tracked) {              //--- If not tracked
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
         AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); //--- Add to list
      }
   }
}

Here, we call the "CheckAndDeleteOppositeOrder" function to manage pending orders, ensuring that if one direction's order is filled, the opposite is deleted as per the "DeleteOppositeOrder" input, preventing conflicting trades.

Next, we add untracked positions to the "positionList" to ensure all relevant open trades are monitored for trailing stops. We loop through all positions with PositionsTotal and PositionGetTicket to get each "ticket". If PositionSelectByTicket succeeds and the position matches _Symbol and "MagicNumber" using "PositionGetString" and "PositionGetInteger", we set a "tracked" flag to false and check the "positionList" array with "ArraySize" and an inner loop to see if the "ticket" is already in "positionList[j].ticket". If not "tracked", we get "openPrice" with PositionGetDouble using POSITION_PRICE_OPEN and call "AddPositionToList" with "ticket", "openPrice", "LondonRangePoints", and "lastCheckedDay". This ensures every matching position is added to the list for management without duplicates. Here is the outcome.

PENDING ORDER DELETED

Since everything is perfect up to this point, we can now define the function to manage these positions as follows.

//+------------------------------------------------------------------+
//| Remove position from tracking list when closed                   |
//+------------------------------------------------------------------+
void RemovePositionFromList(ulong ticket) {
   for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through list
      if (positionList[i].ticket == ticket) { //--- Match ticket
         for (int j = i; j < ArraySize(positionList) - 1; j++) { //--- Shift elements
            positionList[j] = positionList[j + 1]; //--- Copy next
         }
         ArrayResize(positionList, ArraySize(positionList) - 1); //--- Resize array
         break;                   //--- Exit loop
      }
   }
}

//+------------------------------------------------------------------+
//| Manage trailing stops                                            |
//+------------------------------------------------------------------+
void ManagePositions() {
   if (PositionsTotal() == 0 || !UseTrailing) return; //--- Exit if no positions or no trailing
   isTrailing = false;                //--- Reset trailing flag

   double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid
   double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask
   double point = _Point;             //--- Get point value

   for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through positions
      ulong ticket = positionList[i].ticket; //--- Get ticket
      if (!PositionSelectByTicket(ticket)) { //--- Select position
         RemovePositionFromList(ticket); //--- Remove if not selected
         continue;                    //--- Skip
      }

      if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip if not matching

      double openPrice = positionList[i].openPrice; //--- Get open price
      long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type
      double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; //--- Get current price
      double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; //--- Calculate profit points

      if (profitPoints >= MinProfitPoints + TrailingPoints) { //--- Check for trailing
         double newSL = 0.0;          //--- New SL variable
         if (positionType == POSITION_TYPE_BUY) { //--- Buy position
            newSL = currentPrice - TrailingPoints * point; //--- Calculate new SL
         } else {                     //--- Sell position
            newSL = currentPrice + TrailingPoints * point; //--- Calculate new SL
         }
         double currentSL = PositionGetDouble(POSITION_SL); //--- Get current SL
         if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { //--- Check move condition
            if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { //--- Modify position
               positionList[i].trailingActive = true; //--- Set trailing active
               isTrailing = true;        //--- Set global trailing
            }
         }
      }
   }
}

Here, we implement functions to remove closed positions from the tracking list and manage trailing stops. We start with the "RemovePositionFromList" function to clean up the "positionList" array when a position closes, taking "ticket" as a parameter. We loop through "positionList" with ArraySize, and if "positionList[i].ticket" matches "ticket", we shift subsequent elements with an inner loop copying "positionList[j + 1]" to "positionList[j]", then resize the array with ArrayResize to reduce its size by one, and break the loop. This function will ensure the list remains current, avoiding unnecessary checks on closed positions, especially when we trail and close them.

Next, we create the "ManagePositions" function to handle trailing stops for open trades. We exit early if PositionsTotal is 0 or "UseTrailing" is false. We reset "isTrailing" to false, get "currentBid" and "currentAsk" with SymbolInfoDouble using "SYMBOL_BID" and "SYMBOL_ASK", and "point" as "_Point". We loop through "positionList" with "ArraySize", getting "ticket" and selecting it with the PositionSelectByTicket function. If selection fails, we call "RemovePositionFromList" and continue. If the position doesn't match "_Symbol" or "MagicNumber" with "PositionGetString" and "PositionGetInteger", we skip. We get "openPrice" from "positionList[i]", "positionType" with PositionGetInteger, "currentPrice" based on type, and "profitPoints" as the difference divided by point.

If "profitPoints" meets or exceeds "MinProfitPoints + TrailingPoints", we calculate "newSL" as "currentPrice - TrailingPoints * point" for buys or plus for sells. We get "currentSL" with PositionGetDouble, and if "newSL" is better than "currentSL" by at least "point", we modify the position with "obj_Trade.PositionModify" using normalized "newSL" and current TP from "PositionGetDouble". On success, we set "positionList[i].trailingActive" and "isTrailing" to true, allowing us to dynamically adjust stops to lock in profits while allowing winning trades to run. Now, just call the function to manage the positions on every tick. Upon compilation, we have the following outcome.

POSITION MANAGEMENT

From the image, we can see that we check for trading conditions, place orders, and manage them as per the trading strategy, hence achieving our objectives. The thing that remains is backtesting the program, and that is handled in the next section.


Backtesting

After thorough backtesting, we have the following results.

Backtest graph:

GRAPH

Backtest report:

REPORT


Conclusion

In conclusion, we have developed a London Session Breakout System in MQL5 that analyzes pre-session ranges to place pending orders with customizable risk-to-reward ratios, trailing stops, and multi-trade limits, complemented by a control panel for real-time monitoring of ranges, levels, and drawdown. Through modular components like the "PositionInfo" structure, this program offers a disciplined approach to breakout trading that you can refine by adjusting session times or risk parameters.

Disclaimer: This article is for educational purposes only. Trading carries significant financial risks, and market volatility may result in losses. Thorough backtesting and careful risk management are crucial before deploying this program in live markets.

By leveraging the concepts and implementation presented, you can adapt this breakout system to your trading style, empowering your algorithmic strategies. Happy trading! 

Attached files |
MQL5 Wizard Techniques you should know (Part 76):  Using Patterns of Awesome Oscillator and the Envelope Channels with Supervised Learning MQL5 Wizard Techniques you should know (Part 76): Using Patterns of Awesome Oscillator and the Envelope Channels with Supervised Learning
We follow up on our last article, where we introduced the indicator couple of the Awesome-Oscillator and the Envelope Channel, by looking at how this pairing could be enhanced with Supervised Learning. The Awesome-Oscillator and Envelope-Channel are a trend-spotting and support/resistance complimentary mix. Our supervised learning approach is a CNN that engages the Dot Product Kernel with Cross-Time-Attention to size its kernels and channels. As per usual, this is done in a custom signal class file that works with the MQL5 wizard to assemble an Expert Advisor.
Reimagining Classic Strategies (Part 14): Multiple Strategy Analysis Reimagining Classic Strategies (Part 14): Multiple Strategy Analysis
In this article, we continue our exploration of building an ensemble of trading strategies and using the MT5 genetic optimizer to tune the strategy parameters. Today, we analyzed the data in Python, showing our model could better predict which strategy would outperform, achieving higher accuracy than forecasting market returns directly. However, when we tested our application with its statistical models, our performance levels fell dismally. We subsequently discovered that the genetic optimizer unfortunately favored highly correlated strategies, prompting us to revise our method to keep vote weights fixed and focus optimization on indicator settings instead.
Population ADAM (Adaptive Moment Estimation) Population ADAM (Adaptive Moment Estimation)
The article presents the transformation of the well-known and popular ADAM gradient optimization method into a population algorithm and its modification with the introduction of hybrid individuals. The new approach allows creating agents that combine elements of successful decisions using probability distribution. The key innovation is the formation of hybrid population individuals that adaptively accumulate information from the most promising solutions, increasing the efficiency of search in complex multidimensional spaces.
MQL5 Trading Tools (Part 5): Creating a Rolling Ticker Tape for Real-Time Symbol Monitoring MQL5 Trading Tools (Part 5): Creating a Rolling Ticker Tape for Real-Time Symbol Monitoring
In this article, we develop a rolling ticker tape in MQL5 for real-time monitoring of multiple symbols, displaying bid prices, spreads, and daily percentage changes with scrolling effects. We implement customizable fonts, colors, and scroll speeds to highlight price movements and trends effectively.