Русский Español Português
preview
Implementation of a Breakeven Mechanism in MQL5 (Part 1): Base Class and Fixed-Points Breakeven Mode

Implementation of a Breakeven Mechanism in MQL5 (Part 1): Base Class and Fixed-Points Breakeven Mode

MetaTrader 5Examples |
482 0
Niquel Mendoza
Niquel Mendoza


Introduction

Moving a Stop Loss to breakeven is a technique used in trading to manage open positions more safely. It involves moving the Stop Loss level to the trade's opening price after the price has moved a certain number of points in profit. This helps protect the position and reduce the loss in case of an unexpected retracement.

The application of a breakeven mechanism involves two main approaches:

  • it allows you to ensure that if the price does not reach the target Take Profit level, the trade will not close at a loss;
  • it is used to lock in profits by setting the Stop Loss a few points above the entry price.

In this series of articles, we will develop three variations of the breakeven mechanism. Now, in the first part, we will create the base class of the system and program the first simple type of breakeven, which will serve as a template for future extensions.


What is the breakeven mode

Before integrating the concept of breakeven into our system, it is important to understand the basic mechanism of how it works.

In a simplified form, the breakeven mode involves moving the Stop Loss level by a fixed number of points (additional points) after the price has moved a certain distance from the trade's opening price.


    be1

    FIgure 1. A sell position before modifying the Stop Loss level

    The first figure shows that the Stop Loss is initially placed at the level of the signal generated by the Order Blocks indicator (an indicator developed in previous articles of another series).

     be2

    FIgure 2. A sell position after modifying the Stop Loss level

    Figure 2 shows how, after the appearance of a new candle, the system automatically moves the Stop Loss level to a specified distance, corresponding to the set 150 points. This behavior reflects the activation process described above.

    Next, we will examine what happened after the activation of the breakeven mode.

    be3


    Figure 3: Sell position after closing at the breakeven level

    As can be seen in Figure 3, the position closed at the breakeven level with a small profit due to the previously configured adjustment. In this particular case, a total loss (Stop Loss) was avoided because the price did not travel a significant distance from the entry point.

    This is one of the advantages of the breakeven mechanism: it allows for potential profit or helps avoid total losses.
    However, it is important to note that securing a profit depends on how many additional points the breakeven level is set at.

    On the other hand, there are also some disadvantages to using this method. Sometimes, if the Stop Loss had not been moved, the trade could have reached its Take Profit target. This situation largely depends on the strategy being used.

    In our case, when working with Order Blocks signals, applying the breakeven mode is generally advisable because there are instances where order blocks do not work out, making the protection of open positions critically important.

    Breakeven mode based on ATR

    Another management option is to dynamically move the Stop Loss to breakeven based on the ATR (Average True Range).

    This method is similar to the previous one, but instead of using a fixed number of points, it uses a multiplier parameter. The multiplier allows you to calculate the optimal distance for adjusting the Stop Loss, taking into account the current market volatility. This approach proves to be more flexible when dealing with highly volatile assets such as gold.

    For example, if a fixed breakeven level of 150 points (equivalent to $1.50 in gold) is set, then during periods leading up to economic news releases, the price of gold can easily move $3.00 or higher. In such situations, using dynamic adjustment based on ATR helps to better adapt to market behavior, preventing normal fluctuations from closing the trade prematurely.

    Thus, the ATR-based method does not use fixed distances but dynamically adapts to current market conditions.

    Breakeven mode based on the Risk-Reward Ratio (RRR)

    Finally, you can move the Stop Loss to breakeven based on the Risk-Reward Ratio (RRR). RRR represents the relationship between the risk taken and the expected profit from a trade. It is calculated by dividing the Take Profit value by the Stop Loss value.

    Understanding RRR is important for better position management:

    • when the RRR is higher, the percentage of successful trades usually decreases, as the price needs to travel a greater distance to reach the target;
    • when the RRR is lower, the probability of reaching the Take Profit increases, but potential losses may be higher if the market moves against you.

      The logic behind applying the breakeven mode based on RRR is simple. Suppose a position is opened with a Take Profit that is twice the size of the Stop Loss, meaning an RRR of 1:2.

      Using this method, the Stop Loss can be configured to move to the entry price when the trade reaches a 1:1 ratio relative to the initial risk. This means that once the market has advanced enough to offset the risk taken, the trade is automatically protected.

      For example:

      • upon reaching a 1:1 ratio, the Stop Loss can be moved to protect the position;
      • It can also be configured to advance further, for instance, at ratios of 1:2, 1:3, etc., depending on the type of strategy used.

        This approach allows for protecting trades after the market has shown sufficient movement in a favorable direction, based on a specific risk-reward ratio.


        Required structures and enumerations

        In this section, we will begin programming the fundamentals of the breakeven mechanism. Before creating the main class, we will define the necessary structures and enumerations. The process will be clear, simple, and straightforward. We will not use all the enumerations we created for risk management, as we only need a few of them to implement the breakeven feature. Let's get started.

        We will create a simple structure to store the core information that will allow us to apply the breakeven mode. This structure must contain:

        • trade ticket,
        • price_to_beat, i.e., the price the market must exceed to move the Stop Loss to the breakeven_price,
        • which will become the new level where the Stop Loss is set after the condition is met,
        • operation type to correctly determine the levels.

          The structure will look like this:

          struct position_be
           {
            ulong              ticket;           //Position Ticket
            double             breakeven_price;  //Be price
            double             price_to_beat;    //Price to exceed to reach break even
            ENUM_POSITION_TYPE type;             //Position type
           };

          Enumeration for breakeven type

          We have created an enumeration that allows you to select one of the three types of breakeven activation mentioned earlier, designed to optimize the management of each trade:

          enum ENUM_BREAKEVEN_TYPE
           {
            BREAKEVEN_TYPE_RR = 0,           //By RR
            BREAKEVEN_TYPE_FIXED_POINTS = 1, //By FixedPoints
            BREAKEVEN_TYPE_ATR = 2           //By Atr
           };

          Enumeration for breakeven mode

          Not all trading strategies work the same way. Therefore, we have created an enumeration that allows you to unambiguously choose how the breakeven mode will be applied to open trades.

          enum ENUM_BREAKEVEN_MODE
           {
            BREAKEVEN_MODE_AUTOMATIC,
            BREAKEVEN_MODE_MANUAL
           };

          • The BREAKEVEN_MODE_AUTOMATIC mode automatically tracks all new trades using OnTradeTransaction. The trader does not need to do anything extra. The system instantly detects, protects, and manages the trades.
          • The BREAKEVEN_MODE_MANUAL mode provides maximum configuration flexibility. The trader selects which tickets will be protected by the breakeven mechanism and at what point the review of conditions will begin. This mode is ideal for strategies requiring manual selection, such as discrete scalping or high-precision trading setups.


          Creating the base class CBreakEvenBase

          Before starting to define the base class, we will include the risk management system developed in previous articles.

          #include <Risk_Management.mqh>

          Protected Variables of CBreakEvenBase

          Below is the definition of the class and its internal variables. An explanation is also provided for each variable.

          //+------------------------------------------------------------------+
          //| Main class to apply break even                                   |
          //+------------------------------------------------------------------+
          class CBreakEvenBase
           {
          protected:
            CTrade             obj_trade;     //CTrade object
            MqlTick            tick;          //tick structure
            string             symbol;        //current symbol
            double             point_value;   //value of the set symbol point
            position_be        PositionsBe[];  //array of positions of type Positions
            ulong              magic;         //magic number of positions to make break even
            bool               pause;         //Boolean variable to activate the pause of the review, this is used to prevent the array from going out of range
            int                num_params;    //Number of parameters the class needs
            ENUM_BREAKEVEN_MODE breakeven_mode; //Break even mode, manual or automatic
            bool               allow_extra_logs;
            .
            .
           };

          The variable that sends and modifies orders. 

          CTrade             obj_trade;    //CTrade object

          The MqlTick structure, which will store information about the last recorded tick. 

          MqlTick            tick;         //tick structure

          The name of the instrument being analyzed. 

          string             symbol;       //current symbol

          The value of one point for the selected symbol. 

          double             point_value;  //value of the set symbol point

          The array of positions to which the breakeven mode will be applied. 

          position_be        PositionsBe[]; //array of positions of type Positions

          The unique identifier for trades (magic number). 

          ulong              magic;        //magic number of positions to make break even

          An indicator that temporarily pauses the check. 

          bool               pause;         //Boolean variable to activate the pause of the review, this is used to prevent the array from going out of range

          The number of parameters required by the class.

          int                num_params;    //Number of parameters the class needs

          The breakeven application mode (manual or automatic).

          ENUM_BREAKEVEN_MODE breakeven_mode; //Break even mode, manual or automatic

          The ability to create additional log entries.

          bool               allow_extra_logs;

          CBreakEvenBase Functions

          In this section, we begin defining the functions that will be inherited by the various classes implemented later.

          Constructor

          The constructor is a key part of any class. In the CBreakEvenBase class, its task is to initialize the internal variables and assign them the necessary values so that the class functions correctly.

          This constructor accepts three parameters: symbol, from which the bid and ask data will be taken; magic, which allows for the identification of specific orders; and mode, which indicates the selected type of breakeven management (it can be automatic or manual).

          CBreakEvenBase(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_);

          Constructor definition

          When the constructor is executed, the pause and allow_extra_logs variables are initialized with the value false. Then, it checks whether the received mode value is valid. If it is not, an error message is printed, and ExpertRemove is called to remove the expert advisor from the chart, preventing the code from continuing to execute with an incorrect setting.

          //+------------------------------------------------------------------+
          //| Constructor                                                       |
          //+------------------------------------------------------------------+
          CBreakEvenBase::CBreakEvenBase(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
            : pause(false), allow_extra_logs(false)
           {
            if(magic_ != NOT_MAGIC_NUMBER)
              obj_trade.SetExpertMagicNumber(magic_);
          
            if(mode_ != BREAKEVEN_MODE_MANUAL && mode_ != BREAKEVEN_MODE_AUTOMATIC)
             {
              printf("%s:: Error critico el modo del break even %s, es invalido", __FUNCTION__, EnumToString(mode_));
              ExpertRemove();
             }
          
            this.symbol = symbol_;
            this.num_params = 0;
            this.magic = magic_;
            this.breakeven_mode = mode_;
            this.point_value = SymbolInfoDouble(symbol_, SYMBOL_POINT);
           }
          

          The corresponding values are also assigned to the symbol, magic, and breakeven_mode parameters using the values received as arguments.

          The point value of the symbol is stored in the point_value variable, and the number of parameters is initialized with the value 0. Each class inherited from CBreakEvenBase will define its own number of parameters in its own constructor.

          Additionally, if the magic_ variable equals NOT_MAGIC_NUMBER, we will not execute the CTrade class member function intended for setting the magic number. 

          Note: NOT_MAGIC_NUMBER is a define created inside the risk management include file. This define serves to indicate that the magic number will not be used. Therefore, if the breakeven mechanism is used, it will be applied to all trades (only if the breakeven mode is automatic).

          Destructor

          The destructor is used to free the memory used by the array that stores the managed positions. This function is executed automatically when the object is destroyed.

          //+------------------------------------------------------------------+
          //| Destructor                                                       |
          //+------------------------------------------------------------------+
          CBreakEvenBase::~CBreakEvenBase()
           {
            ArrayFree(PositionsBe);
           }


          Implementing Common Class Functions

          Next, we will define the functions that are part of the base class CBreakEvenBase. These functions will be used and extended by the derived classes that will be implemented later.

          To begin, we will declare a function that returns the number of parameters required by the class. This function simply returns the value of the protected variable num_params. Additionally, it is marked as const to prevent modifying the state of the object, and as final to prevent it from being overridden by child classes.

          virtual inline int GetNumParams() const final { return num_params; }

          Next, we declare the Add function, which will be used to add a new position_be structure to the PositionsBe array. This function will receive the necessary trade data, such as the ticket, opening price, Stop Loss, and position type.

          virtual bool       Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type) = 0;

          Each class inheriting from CBreakEvenBase must implement its own version of this function, as it is declared as pure and virtual.

          The main function of this class is to ensure breakeven. It is responsible for applying the breakeven adjustment to all positions stored in the PositionsBe array. To do this, the array is iterated over using a for loop.

          Before applying the adjustment, it checks that the array size is greater than zero and that the pause variable is false. If these conditions are not met, the function immediately exits.

          Since the position_be structure already contains both the target level and the price at which the adjustment should be triggered, it is only necessary to check whether the market condition is met:

          • if the position is a buy, it checks whether the ask price is greater than or equal to the price_to_beat value;
          • if it is a sell, it checks whether the bid price is less than or equal to the same value.

          After confirming the condition, the position is identified using its corresponding ticket, and the current Take Profit value is calculated. Then, the modification is performed using the PositionModify method of the CTrade class. Finally, this position is marked for removal from the array.

          //+------------------------------------------------------------------+
          //| Function to make break even                                      |
          //+------------------------------------------------------------------+
          void CBreakEvenBase::BreakEven(void)
           {
            if(this.PositionsBe.Size() < 1 || pause)
              return;
          
            SymbolInfoTick(this.symbol, tick);
          
            int indices_to_remove[];
          
            for(int i = 0 ; i < ArraySize(this.PositionsBe) ; i++)
             {
              if((this.PositionsBe[i].type == POSITION_TYPE_BUY && tick.ask >= this.PositionsBe[i].price_to_beat) ||
                 (this.PositionsBe[i].type == POSITION_TYPE_SELL && tick.bid <= this.PositionsBe[i].price_to_beat))
              {
                  if(!PositionSelectByTicket(this.PositionsBe[i].ticket))
                  {
                   printf("%s:: Error al seleccionar el ticket %I64u",__FUNCTION__,this.PositionsBe[i].ticket);
                   ExtraFunctions::AddArrayNoVerification(indices_to_remove, i);
                   continue;
                  }
                  
                  double position_tp = PositionGetDouble(POSITION_TP);
                  obj_trade.PositionModify(this.PositionsBe[i].ticket, this.PositionsBe[i].breakeven_price, position_tp);
                  ExtraFunctions::AddArrayNoVerification(indices_to_remove, i);
              }
             }
          
            ExtraFunctions::RemoveMultipleIndexes(this.PositionsBe, indices_to_remove);
           }

          To add a structure to the PositionsBe array when a new trade is opened, we define a function that will be called inside the OnTradeTransaction event. This function is named OnTradeTransactionEvent and will be executed every time a trading transaction occurs.

          virtual void       OnTradeTransactionEvent(const MqlTradeTransaction& trans) final;

          Before adding a position to the PositionsBe array, it is necessary to select its corresponding ticket using the HistoryDealSelect function. Then, the trade entry type is determined using the entry value.

          If the entry corresponds to a market entry, i.e., DEAL_ENTRY_IN, it checks that the deal is fully opened, that the breakeven mode is automatic — BREAKEVEN_MODE_AUTOMATIC, and that the deal's magic number matches the internal magic variable, or that the latter has the value NOT_MAGIC_NUMBER.

          When all conditions are met, the position is added to the PositionsBe array using the Add function. If allow_extra_logs is activated, a message indicating the position registration is printed.

          In the case where the entry corresponds to an exit, i.e., DEAL_ENTRY_OUT, and the position is fully closed, the corresponding ticket is removed from the PositionsBe array. To avoid potential conflicts during this removal, the pause variable is temporarily set to true.

          The code of the function is structured as follows:

          //+------------------------------------------------------------------+
          //| OnTradeTransactionEvent                                          |
          //+------------------------------------------------------------------+
          void CBreakEvenBase::OnTradeTransactionEvent(const MqlTradeTransaction &trans)
           {
            if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
              return;
          
            HistoryDealSelect(trans.deal);
            ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
            bool pos = PositionSelectByTicket(trans.position);
          
            if(breakeven_mode == BREAKEVEN_MODE_AUTOMATIC)
             {
              ulong position_magic = (ulong)HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
          
              if(entry == DEAL_ENTRY_IN && pos && (this.magic == position_magic || this.magic == NOT_MAGIC_NUMBER))
               {
                if(Add(trans.position, PositionGetDouble(POSITION_PRICE_OPEN), PositionGetDouble(POSITION_SL), (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)))
                  if(this.allow_extra_logs)
                    printf("%s:: Se a añadido el ticket %I64u del array de posiciones", __FUNCTION__, trans.position);
                return;
               }
             }
          
            if(entry == DEAL_ENTRY_OUT && pos == false)
             {
              this.pause = true;
          
              if(ExtraFunctions::RemoveIndexFromAnArrayOfPositions(PositionsBe, trans.position))
                if(this.allow_extra_logs)
                  printf("%s:: Se a eliminado el ticket %I64u del array de posiciones", __FUNCTION__, trans.position);
          
              this.pause = false;
             }
           }

          Some derived classes will require the configuration of various parameters. For example, a simple adjustment needs two integer values, while other methods, such as the ATR-based method, require data like the period, timeframe, and multiplier.

          Since it is not possible to define multiple versions of a single virtual function with different parameters in the base class, it is proposed to use an array of type MqlParam to store the necessary configuration. This way, each inheriting class can internally interpret and assign the received values.

          virtual void       Set(MqlParam &params[]) = 0;

          Finally, a function is added to allow enabling or disabling additional informational messages. This is useful when you need to log actions such as the addition or removal of tickets during program execution.

          virtual void       SetExtraLogs(bool allow_extra_logs_) final { this.allow_extra_logs = allow_extra_logs_; }


          Developing the First Class CBreakEvenSimple

          Internal variables

          Now that the base class is ready, we will begin developing the CBreakEvenSimple class. This class applies the breakeven method using a fixed number of points.

          For this, two variables are declared:

          int                extra_points_be, points_be;

          • the variable extra_points_be indicates the additional distance the breakeven level will be moved after activation,
          • the variable points_be determines how many points in profit the price must move before the adjustment is executed.

          Constructor

          In the constructor, the initial values must be set. The base class constructor is also called to assign the inherited parameters. Inside the constructor body, the internal variables are initialized to zero, and the number of parameters required by the class is defined, which in this case is two.

                               CBreakEvenSimple(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
          :                CBreakEvenBase(symbol_, magic_, mode_) { this.extra_points_be = 0; this.points_be = 0; this.num_params = 2;}

          Function for setting parameters

          To assign values to the internal variables extra_points_be and points_be, an array of type MqlParam will be used. This structure is part of the MQL5 language and is typically used when adding indicators via the ChartIndicatorAdd() function.

          In this case, it will be used to pass values to the class. The Set function will be overridden from the base class. The keyword "override" is used for this.

          void               Set(MqlParam &params[]) override;

          Inside the function, it checks that the size of the params array is not less than two. If this condition is not met, an informational error message is displayed. Otherwise, the SetSimple function is called to assign the corresponding values.

          //+------------------------------------------------------------------+
          //| Set attributes of CBreakEvenSimple class with MqlParam array     |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::Set(MqlParam &params[])
           {
            if(params.Size() < 2)
             {
              printf("%s:: Error setting simple break-even, the size of the params array %I32u to less than 2", __FUNCTION__, params.Size());
              return;
             }
          
            SetSimple(int(params[0].integer_value), int(params[1].integer_value));
           }

          The SetSimple function

          The SetSimple function allows directly setting the values for points_be and extra_points_be without using a parameter array. In this case, the integer values are accepted directly when the function is called.

          //+------------------------------------------------------------------+
          //| Function to set member variables without using MalParams         |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::SetSimple(int points_be_, int extra_points_be_)
           {
            if(points_be_ <= 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, be points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ < 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, extra points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ >= points_be_)
             {
              printf("%s:: Warning: The break even points (breakeven_price) is greater than the breakeven points (price_to_beat)\nTherefore the value of the extra breakeven points will be modified 0.",
                     __FUNCTION__);
              this.points_be = points_be_; //0
              this.extra_points_be = 0; //1
              return;
             }
          
            this.points_be = points_be_; //0
            this.extra_points_be = extra_points_be_; //1
           }

          This method can be useful if the values are defined directly in the code, or if in some specific cases it is necessary to avoid using the MqlParam type.

          Function for adding position data to the PositionsBe array

          To continue developing the CBreakEvenSimple class, the Add function must be overridden. This function allows calculating and registering the main values of the position_be structure, which will then be stored in the PositionsBe array.

          The open_price parameter is used as a basis for calculating two values: breakeven_price and price_to_beat. These variables define the level to which the Stop Loss will be moved, and the level the price must reach to trigger the adjustment.

          The following formulas are used to calculate the breakeven_price value:

          • For buy positions:

          break_even_price = open_price + (point_value * extra_points_be)

          • For sell positions:

          break_even_price = open_price - (point_value * extra_points_be)

          On the other hand, to calculate price_to_beat, the same formulas are used, but the variable extra_points_be is replaced by points_be.

          After determining these values, the position_be structure is filled with the necessary data. This structure is then added to the PositionsBe array using the AddArrayNoVerification function, which was already used in the previous section on risk management.

          The implementation code looks like this:

          //+----------------------------------------------------------------------------------------------+
          //| Create a new structure and add it to the main array using the 'AddToArrayBe' function        |
          //+----------------------------------------------------------------------------------------------+
          bool CBreakEvenSimple::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
           {
            position_be new_pos;
            new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (point_value * extra_points_be) : open_price - (point_value * extra_points_be);
            new_pos.type =  position_type;
            new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (point_value * points_be) : open_price - (point_value * points_be) ;
            new_pos.ticket = post_ticket;
            ExtraFunctions::AddArrayNoVerification(this.PositionsBe,new_pos); 
            return true;
           }

          Complete class code:

          //+------------------------------------------------------------------+
          //| class CBreakEvenSimple                                           |
          //+------------------------------------------------------------------+
          class CBreakEvenSimple : public CBreakEvenBase
           {
          private:
            int                extra_points_be, points_be;
          
          public:
                               CBreakEvenSimple(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_)
              :                CBreakEvenBase(symbol_, magic_, mode_) { this.extra_points_be = 0; this.points_be = 0; this.num_params = 2;}
          
          
            bool               Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type) override;
            void               Set(MqlParam &params[]) override;
            void               SetSimple(int points_be_, int extra_points_be_);
           };
          
          //+----------------------------------------------------------------------------------------------+
          //| Create a new structure and add it to the main array using the 'AddToArrayBe' function        |
          //+----------------------------------------------------------------------------------------------+
          bool CBreakEvenSimple::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type)
           {
            position_be new_pos;
            new_pos.breakeven_price =  position_type == POSITION_TYPE_BUY ? open_price + (point_value * extra_points_be) : open_price - (point_value * extra_points_be);
            new_pos.type =  position_type;
            new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (point_value * points_be) : open_price - (point_value * points_be) ;
            new_pos.ticket = post_ticket;
            ExtraFunctions::AddArrayNoVerification(this.PositionsBe, new_pos);
            return true;
           }
          
          //+------------------------------------------------------------------+
          //| Set attributes of CBreakEvenSimple class with MqlParam array     |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::Set(MqlParam &params[])
           {
            if(params.Size() < 2)
             {
              printf("%s:: Error setting simple break-even, the size of the params array %I32u to less than 2", __FUNCTION__, params.Size());
              return;
             }
          
            SetSimple(int(params[0].integer_value), int(params[1].integer_value));
           }
          
          //+------------------------------------------------------------------+
          //| Function to set member variables without using MalParams         |
          //+------------------------------------------------------------------+
          void CBreakEvenSimple::SetSimple(int points_be_, int extra_points_be_)
           {
            if(points_be_ <= 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, be points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ < 0)
             {
              printf("%s:: Error when setting the break even value for fixed points, extra points %I32d are invalid.", __FUNCTION__, extra_points_be_);
              ExpertRemove();
              return;
             }
          
            if(extra_points_be_ >= points_be_)
             {
              printf("%s:: Warning: The break even points (breakeven_price) is greater than the breakeven points (price_to_beat)\nTherefore the value of the extra breakeven points will be modified 0.",
                     __FUNCTION__);
              this.points_be = points_be_; //0
              this.extra_points_be = 0; //1
              return;
             }
          
            this.points_be = points_be_; //0
            this.extra_points_be = extra_points_be_; //1
           }
          //+------------------------------------------------------------------+

          This completes the basic implementation of the fixed-point breakeven method. In the next section, the functionality of this class will be tested using the Order Blocks expert advisor, developed in the last article on risk management.


          Testing the breakeven mode

          In this section, we will adapt the Order Blocks expert advisor to integrate the application of the fixed-point breakeven mode. The goal is to verify the system's operation and evaluate the behavior of positions under such active management.

          To begin, we modify the advisor's parameters, adding a new specialized section:

          sinput group "-----| Break Even |----"

          In this section, all parameters related to the breakeven mechanism will be placed. Since only the fixed-point variant has been developed so far, a general bool parameter is added to activate or deactivate it.

          input bool use_be = true;                         // Enable Break Even usage

          Next, a subsection for the specific parameters of this method will be defined.

          sinput group "- BreakEven based on Fixed Points -"
          input int be_fixed_points_to_put_be = 200;         // Points traveled needed to activate Break Even
          input int be_fixed_points_extra = 100;             // Extra adjustment points when activating Break Even
          

          To integrate the functionality, an object of type CBreakEvenSimple is created. In the future, this will be replaced by a more flexible manager type object, which will allow managing different variants from a single interface.

          CBreakEvenSimple break_even(_Symbol, Magic, BREAKEVEN_MODE_AUTOMATIC);

          In the OnInit function, if the use_be parameter is enabled, the values configured via the SetSimple function will be assigned.

          //---
            if(use_be)
              break_even.SetSimple(be_fixed_points_to_put_be, be_fixed_points_extra);
          

          For the breakeven management to work correctly, the OnTradeTransactionEvent function of the break_even object must be executed inside the OnTradeTransaction function. It will be responsible for registering or removing deals in the corresponding array.

          //+------------------------------------------------------------------+
          //| TradeTransaction function                                        |
          //+------------------------------------------------------------------+
          void OnTradeTransaction(const MqlTradeTransaction& trans,
                                  const MqlTradeRequest& request,
                                  const MqlTradeResult& result)
           {
            risk.OnTradeTransactionEvent(trans);
            break_even.OnTradeTransactionEvent(trans);
           }
          

          Finally, inside OnTick, the breakeven function will only be called if the use of this mechanism is enabled by the user:

          if(use_be) break_even.BreakEven();   

          Thanks to these changes, the robot can now automatically apply the breakeven mechanism when the specified conditions are met. 

          Backtest

          The backtest of the Order Blocks expert advisor with the breakeven mode was conducted without capital management restrictions. No profit or loss limits were set. Trades could only close when the Stop Loss or Take Profit level was reached. A Risk:Reward ratio of 1:2 was used, where the Take Profit is twice the Stop Loss.

          1. General settings:

              FIgure 4. General settings for the backtest

          2. Backtest chart of the strategy operating without the breakeven mechanism.

          FIgure 5. Backtest chart without using the breakeven mechanism

          Figure 5 shows the behavior of the advisor without using the breakeven mechanism. During this test, the initial balance of $15,000 decreased to $8,700, resulting in a loss of approximately $7,000. This result serves as a benchmark for evaluating the consequences of enabling or disabling this feature.

          3. Backtest chart of the strategy operating without the breakeven mechanism.

          FIgure 6. Backtest chart without using the breakeven mechanism

          The second test was conducted with the breakeven mode activated. The be_fixed_points_extra parameter was set to 100 points, and the be_fixed_points_to_put_be parameter was set to 600 points.

          During this testing, the chart showed a slower progression. In series of losing trades, the balance decline was smaller. The balance decreased from $10,900 to $7,900, representing a $3,000 loss. This $3,000 difference compared to the first scenario indicates that using the breakeven mechanism can help reduce the impact of losing streaks.

          However, limitations were also identified. In both tests, trades started with losses until approximately March 2024. Starting from 01.03.2024, changes were observed: a series of profitable trades appeared. In the first backtest, the balance exceeded $16,000, whereas in the second, with the breakeven mode activated, it only reached $10,900.

          One possible explanation is early closures caused by the breakeven mode. If, after opening a position, the price retraces but does not reach the Stop Loss level, then in the absence of a breakeven mechanism, it may reverse in the expected direction and close with a profit. Conversely, with the breakeven mode activated, the position would close at zero, eliminating the possibility of capturing that profit.

          This suggests that in the first scenario, many trades remained in a negative balance until reaching Take Profit. For this reason, when applying the fixed-point breakeven mode, the result may be limited in scenarios where price retracements occur frequently.

          In conditions of high volatility, this configuration can activate quickly, protecting capital, but at the same time prematurely closing trades. In periods of lower activity, it may not trigger at all. Thus, using the fixed-point breakeven mode is more suitable for traders who prioritize balance protection over achieving higher profit per trade.


          Conclusion

          In this article, we analyzed the concept of breakeven, its implementation in MQL5, and some possible variations. Everything developed was applied to the Order Blocks expert advisor, created in the last article on risk management.

          To conclude the analysis, a comparison was made of the system's behavior with the breakeven mode activated and without it. It was observed that during series of profitable trades, its use could limit balance growth. Conversely, during losing streaks, the breakeven mode reduces exposure to large losses. This duality indicates the existence of a certain balance between risk and result, although the real effectiveness will depend on the statistical results of the underlying strategy without external constraints (such as profit or loss limits).

          We also concluded that using the breakeven mode is more suitable for traders who do not seek aggressive trading, but rather prefer a steady progression of results with greater control over trade exits.

          Files used/updated in this article:

          File name Type Description 
           Risk_Management.mqh  .mqh (header file) Contains the risk management class developed in the last article of the risk management series.
           Order_Block_Indicador_New_Part_2.mq5 .mq5 (indicator) Contains the code for the Order Block indicator.
           Order Block EA MetaTrader 5.mq5  .mq5 (expert advisor) The code for the Order Block robot with integrated breakeven mode.
           OB_SET_WITHOUT_BREAKEVEN.set .set (configuration file) Settings for the first backtest, without the breakeven mechanism.
           OB_SET_WITH_BREAKEVEN.set .set (configuration file) Settings for the second backtest with the breakeven mode activated.
           PositionManagement.mqh .mqh (header file) The mqh file containing the code for the breakeven mechanism.


          Translated from Spanish by MetaQuotes Ltd.
          Original article: https://www.mql5.com/es/articles/17957

          Attached files |
          MQL5.zip (210.17 KB)
          Features of Custom Indicators Creation Features of Custom Indicators Creation
          Creation of Custom Indicators in the MetaTrader trading system has a number of features.
          Larry Williams Market Secrets (Part 12): Context Based Trading of Smash Day Reversals Larry Williams Market Secrets (Part 12): Context Based Trading of Smash Day Reversals
          This article shows how to automate Larry Williams Smash Day reversal patterns in MQL5 within a structured context. We implement an Expert Advisor that validates setups over a limited window, aligns entries with Supertrend-based trend direction and day-of-week filters, and supports entry on level cross or bar close. The code enforces one position at a time and risk-based or fixed sizing. Step-by-step development, backtesting procedure, and reproducible settings are provided.
          Features of Experts Advisors Features of Experts Advisors
          Creation of expert advisors in the MetaTrader trading system has a number of features.
          From Novice to Expert:  Extending a Liquidity Strategy with Trend Filters From Novice to Expert: Extending a Liquidity Strategy with Trend Filters
          The article extends a liquidity-based strategy with a simple trend constraint: trade liquidity zones only in the direction of the EMA(50). It explains filtering rules, presents a reusable TrendFilter.mqh class and EA integration in MQL5, and compares baseline versus filtered tests. Readers gain a clear directional bias, reduced overtrading in countertrend phases, and ready-to-use source files.