preview
Implementación de breakeven en MQL5 (Parte 1): Clase base y breakeven por puntos fijos

Implementación de breakeven en MQL5 (Parte 1): Clase base y breakeven por puntos fijos

MetaTrader 5Ejemplos |
388 0
Niquel Mendoza
Niquel Mendoza


Introducción

El breakeven es una técnica utilizada en trading para gestionar de forma más segura las posiciones abiertas. Consiste en mover el nivel de stop loss al precio de apertura de la operación una vez que el precio haya alcanzado una cierta cantidad de puntos favorables. De esta manera, se busca proteger la operación y minimizar las pérdidas en caso de un retroceso inesperado.

Aplicar el breakeven ofrece dos enfoques principales:

  • Permite asegurar que, si el precio no alcanza el objetivo de beneficios takeprofit, la operación no termine en pérdida.
  • También puede utilizarse para asegurar ganancias al ajustar el stop loss unos puntos más arriba del precio de entrada.

En esta serie de artículos, desarrollaremos tres tipos de mecanismos de breakeven.
En esta primera parte, crearemos la clase base del sistema de breakeven y programaremos un primer tipo de breakeven sencillo, que servirá como plantilla para futuras extensiones.


¿Qué es el breakeven?

Antes de integrar el concepto de breakeven en nuestro sistema, es importante comprender su funcionamiento básico.

De forma simple, el breakeven consiste en mover el nivel de stoploss una cantidad fija de puntos (puntos extra) una vez que el precio se ha desplazado una cierta distancia desde el precio de apertura de la operación.


    be1

    Imagen 1: Posición de venta antes de la modificación del stop loss

    En la primera imagen, se observa que el stop loss inicialmente se encuentra en la señal generada por el indicador de Order Blocks (indicador desarrollado en artículos anteriores de otra serie).

     be2

    Imagen 2: Posición de venta después de la modificación del stop loss

    En la siguiente imagen, se muestra cómo, después de una nueva vela, el sistema mueve automáticamente el stop loss a la distancia configurada, correspondiente a los 150 puntos establecidos. Este comportamiento refleja el proceso de activación que se explicó anteriormente.

    A continuación, revisaremos qué ocurrió después de aplicar el breakeven.

    be3


    Imagen 3: Posición de venta luego de haber cerrado en breakeven

    Como se puede observar en la imagen, la posición cerró en breakeven, con una ligera ganancia debido al ajuste configurado previamente.
    En este caso específico, se evitó alcanzar una pérdida completa (stoploss), ya que el precio no recorrió una distancia significativa desde el punto de entrada.

    Esta es una de las ventajas del método de breakeven: permite asegurar ganancias potenciales o evitar pérdidas totales.
    Sin embargo, es importante tener en cuenta que asegurar ganancias depende de cuántos puntos extra se coloque el nivel de breakeven.

    Por otro lado, también existen algunas desventajas al aplicar el breakeven. En ocasiones, si no se hubiera movido el stoploss, la operación podría haber alcanzado su objetivo de beneficios (takeprofit). Esta situación depende en gran medida de la estrategia utilizada.

    En nuestro caso, donde se trabajara con señales de Order Blocks, aplicar el breakeven suele ser recomendable, ya que existen momentos en los que los bloques de orden no se respetan y proteger las posiciones abiertas se vuelve crucial.

    Breakeven basado en ATR

    Otra modalidad de gestión es el breakeven basado en ATR (Average True Range).

    Este método es similar al anterior, pero en lugar de usar un número fijo de puntos, se utiliza el parámetro multiplier. El "multiplier" permite calcular la distancia adecuada para el ajuste del stop loss, tomando en cuenta la volatilidad actual del mercado. Este enfoque resulta más flexible frente a activos de alta volatilidad, como el oro.

    Por ejemplo, si se configura un breakeven fijo de 150 puntos (equivalente a 1.50 USD en oro), en períodos previos a anuncios económicos, el precio del oro puede moverse fácilmente a 3.00 USD o más. En esos escenarios, utilizar un ajuste dinámico basado en ATR ayuda a adaptarse mejor al comportamiento del mercado, evitando que movimientos normales saquen prematuramente a la operación.

    De esta forma, el método basado en ATR no utiliza distancias fijas, sino que se adapta de manera dinámica a las condiciones actuales.

    Breakeven basado en el Risk-Reward Ratio (RRR)

    Finalmente, se puede aplicar un breakeven basado en el Risk-Reward Ratio (RRR). El "RRR" representa la relación entre el riesgo asumido y la recompensa esperada en una operación. Se calcula dividiendo el valor del takeprofit entre el valor del stoploss.

    Comprender el "RRR" es importante para gestionar mejor las posiciones:

    • Cuando el "RRR" es mayor, el porcentaje de operaciones exitosas suele disminuir, ya que el precio necesita recorrer una distancia mayor para alcanzar el objetivo.

    • Cuando el "RRR" es menor, las probabilidades de alcanzar el takeprofit aumentan, pero las pérdidas potenciales pueden ser más elevadas si el mercado se mueve en contra.

    La lógica para aplicar el breakeven usando el "RRR" es simple. Supongamos que se abre una posición con un takeprofit que es el doble del stoploss, es decir, un "RRR" de 1:2.0.

    En este método, se puede configurar que el stoploss se mueva al precio de entrada, cuando la operación haya alcanzado una relación de 1:1 respecto al riesgo inicial. Esto significa que, una vez que el mercado avance a favor lo suficiente como para igualar el riesgo asumido, se proteja automáticamente la operación.

    Por ejemplo:

    • Cuando se alcance una relación de 1:1, se puede mover el stoploss para asegurar la posición.

    • También es posible configurar que se mueva más adelante, como a una relación 1:2, 1:3, etc., según el tipo de estrategia que se esté utilizando.

    Este enfoque permite proteger las operaciones después de que el mercado haya demostrado un avance favorable suficiente, en función del riesgo-recompensa definido.


    Estructuras y enumeraciones necesarias

    En esta sección comenzaremos a programar las bases del breakeven. Antes de crear la clase principal, definiremos las estructuras y enumeraciones necesarias. El proceso será claro, simple y directo.

    No usaremos todas las enumeraciones que creamos para la gestión de riesgo, ya que para el breakeven solo necesitaremos unas pocas. Empecemos.

    Crearemos una estructura sencilla para almacenar la información esencial que permitirá aplicar el breakeven. Esta estructura debe contener:

    • El "ticket" de la operación.
    • El "price_to_beat", es decir, el precio que debe superar el mercado para mover el stoploss al "breakeven_price".
    • El "breakeven_price", que será el nuevo nivel donde se fijará el stoploss una vez cumplida la condición.
    • El "type" de operación, para determinar correctamente los niveles.

      El diseño será el siguiente:

      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
       };

      Enumeración para el tipo de breakeven

      Hemos creado una enumeración que permite seleccionar entre tres tipos de activación de breakeven mencionados anteriormente, diseñados para optimizar la gestión de cada operación:

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

      Enumeración para el modo de breakeven

      No todas las estrategias de trading trabajan igual. Por eso, hemos creado una enumeración que permite seleccionar de manera clara cómo se aplicará el breakeven en las operaciones abiertas.

      enum ENUM_BREAKEVEN_MODE
       {
        BREAKEVEN_MODE_AUTOMATIC,
        BREAKEVEN_MODE_MANUAL
       };

      • El modo "BREAKEVEN_MODE_AUTOMATIC" captura automáticamente todas las operaciones nuevas utilizando "OnTradeTransaction". El trader no tiene que hacer nada adicional. El sistema detecta, protege y gestiona las operaciones de forma inmediata.
      • El modo "BREAKEVEN_MODE_MANUAL" ofrece la máxima personalización. El trader elige qué tickets se protegerán con breakeven y en qué momento empezará la revisión de las condiciones. Este modo es ideal para estrategias que requieren selección manual, como scalping discrecional o setups de alta precisión.


      Creación de la clase base CBreakEvenBase

      Antes de empezar a definir la clase base, incluimos la gestión de riesgo desarrollada en artículos anteriores.

      #include <Risk_Management.mqh>

      Variables protegidas de CBreakEvenBase

      A continuación se muestra la definición de la clase y sus variables internas. Cada variable se explica a continuación.

      //+------------------------------------------------------------------+
      //| 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        PostionsBe[];  //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;
        .
        .
       };

      La variable que envía y modifica órdenes. 

      CTrade             obj_trade;    //CTrade object

      La estructura MqlTick que almacenará la información del último tick registrado. 

      MqlTick            tick;         //tick structure

      El nombre del instrumento analizado. 

      string             symbol;       //current symbol

      El valor de un punto en el símbolo elegido. 

      double             point_value;  //value of the set symbol point

      El array de posiciones para aplicar breakeven. 

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

      El identificador único de las operaciones. 

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

      El indicador que detiene la revisión temporalmente. 

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

      El número de parámetros que requiere la clase.

      int                num_params;    //Number of parameters the class needs

      El modo de aplicación de breakeven (manual o automático).

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

      La opción para generar registros adicionales.

      bool               allow_extra_logs;

      Funciones de CBreakEvenBase

      En esta sección comenzamos a definir las funciones que serán heredadas por las diferentes clases que se implementarán más adelante.

      Constructor

      El constructor es una parte esencial de cualquier clase. En la clase "CBreakEvenBase", su objetivo es inicializar las variables internas y asignar los valores necesarios para que la clase funcione correctamente.

      Este constructor recibe tres parámetros: el "symbol" sobre el cual se tomarán los datos de "bid" y "ask", el "magic" que permite identificar las órdenes específicas, y el "mode" que indica el tipo de gestión de breakeven a aplicar (puede ser automático o manual).

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

      Definición del constructor:

      Al ejecutar el constructor, se inicializan las variables "pause" y "allow_extra_logs" con el valor false. Luego, se verifica que el valor recibido en "mode" sea válido. Si no lo es, se imprime un mensaje de error y se llama a "ExpertRemove" para remover al asesor del gráfico, evitando que el código continúe con una configuración incorrecta.

      //+------------------------------------------------------------------+
      //| Contructor                                                       |
      //+------------------------------------------------------------------+
      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);
       }
      

      También se asigna el valor correspondiente al "symbol", al "magic", y al "breakeven_mode" utilizando los valores recibidos como parámetros.

      Se guarda el valor del punto del símbolo en "point_value", y se inicializa el número de parámetros con el valor "0". Cada clase que herede de "CBreakEvenBase" definirá su propio número de parámetros dentro de su propio constructor.

      Además, si la variable magic_ es "NOT_MAGIC_NUMBER", no ejecutaremos la función miembro de "CTrade" para establecer el número mágico. 

      Nota: la palabra "NOT_MAGIC_NUMBER" es un define que se ha creado dentro del include de gestión de riesgo, este define sirve para representar que no se usara un número mágico, por lo tanto, en el caso del breakeven se les aplicaría a todas las operaciones (esto solo si el modo del breakeven es automático).

      Destructor

      El destructor se utiliza para liberar la memoria utilizada por el array que almacena las posiciones gestionadas. Esta función se ejecuta automáticamente cuando el objeto es destruido.

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


      Implementación de las funciones generales de la clase

      A continuación, definiremos las funciones que forman parte de la clase base "CBreakEvenBase". Estas funciones serán utilizadas y extendidas por las clases derivadas que se implementarán más adelante.

      Para comenzar, declaramos una función que devuelve el número de parámetros que necesita la clase. Esta función simplemente retorna el valor de la variable protegida "num_params". Además, se marca como const para evitar que modifique el estado del objeto, y como final para que no sea sobrescrita por las clases hijas.

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

      Después, declaramos la función "Add", que será utilizada para agregar una nueva estructura "position_be" al array "PositionsBe". Esta función recibirá los datos necesarios de la operación, como el ticket, el precio de apertura, el stoploss y el tipo de posición.

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

      Cada clase que herede de "CBreakEvenBase" deberá implementar su propia versión de esta función, ya que se declara como pura y virtual.

      La función principal de esta clase es breakeven. Esta se encarga de aplicar el ajuste de breakeven a todas las posiciones almacenadas en el array "PositionsBe". Para ello, se recorre el arreglo utilizando un bucle for.

      Antes de aplicar el ajuste, se verifica que el tamaño del arreglo sea mayor que cero y que la variable "pause" tenga el valor false. Si no se cumplen estas condiciones, la función finaliza inmediatamente.

      Como la estructura "position_be" ya contiene tanto el precio objetivo como el precio al que debe activarse el ajuste, solo es necesario comprobar si la condición de mercado se cumple.

      • Si la posición es de compra, se verifica que el precio ask sea mayor o igual al valor de "price_to_beat".
      • Si es una venta, se verifica que el precio bid sea menor o igual a ese mismo valor.

      Una vez que se confirma la condición, se selecciona la posición mediante su ticket y se obtiene el valor actual del takeprofit. Después, se realiza la modificación utilizando el método "PositionModify" de la clase "CTrade". Finalmente, se marca esa posición para eliminarla del arreglo.

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

      Para agregar una estructura al array "PositionsBe" cuando se abre una nueva operación, definimos una función que será llamada dentro del evento "OnTradeTransaction". Esta función se llama "OnTradeTransactionEvent" y será ejecutada cada vez que ocurra una transacción comercial.

      virtual void       OnTradeTransactionEvent(const MqlTradeTransaction& trans) final;

      Antes de agregar una posición al array "PositionsBe", se debe seleccionar su ticket utilizando la función "HistoryDealSelect". Luego, se evalúa el tipo de entrada de la operación mediante el valor de "entry".

      Si "entry" corresponde a una entrada al mercado, es decir, "DEAL_ENTRY_IN", se verifica que la operación esté completamente abierta, que el modo de ejecución del breakeven sea "BREAKEVEN_MODE_AUTOMATIC", y que el número mágico de la operación coincida con la variable interna "magic" o que esta última tenga el valor "NOT_MAGIC_NUMBER".

      Cuando todas las condiciones se cumplen, la posición se agrega al array "PositionsBe" utilizando la función "Add". Si "allow_extra_logs" está activado, se imprime un mensaje indicando el registro de la posición.

      En el caso en que "entry" sea de salida, es decir, "DEAL_ENTRY_OUT", y la posición haya sido cerrada completamente, se elimina el ticket correspondiente del array "PositionsBe". Para evitar posibles conflictos durante esta eliminación, se asigna temporalmente el valor true a la variable "pause".

      El código de la función queda estructurado de la siguiente manera:

      //+------------------------------------------------------------------+
      //| 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(PostionsBe, 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;
         }
       }

      Algunas clases derivadas requerirán configurar diferentes parámetros. Por ejemplo, el ajuste simple necesita dos valores enteros, mientras que otras versiones como la basada en ATR necesitan datos como período, timeframe y multiplier.

      Dado que no es posible sobrecargar funciones con diferentes parámetros en una misma clase base, se propone utilizar un array de tipo "MqlParam" para almacenar la configuración necesaria. De esta forma, cada clase heredada podrá interpretar y asignar internamente los valores recibidos.

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

      Por último, se añade una función que permite activar o desactivar mensajes informativos adicionales. Esto es útil cuando se desea registrar acciones como la adición o eliminación de tickets durante la ejecución del programa.

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


      Desarrollo de la primera clase CBreakEvenSimple

      Variables internas

      Ahora que la clase base está lista, comenzaremos a desarrollar "CBreakEvenSimple". Esta clase aplica el método de breakeven utilizando una cantidad fija de puntos.

      Para ello, se declaran dos variables:

      int                extra_points_be, points_be;

      • La variable "extra_points_be" representa la distancia adicional a la que se colocará el breakeven, una vez que se active.
      • La variable "points_be" define cuántos puntos debe avanzar el precio a favor antes de activar el ajuste.

      Contructor

      En el constructor se deben establecer los valores iniciales. También se llama al constructor de la clase base para asignar los parámetros heredados. Dentro del cuerpo del constructor, se inicializan las variables internas con cero y se define el número de parámetros requeridos por la clase, que en este caso es dos.

                           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;}

      Función para establecer parámetros

      Para asignar valores a las variables internas "extra_points_be" y "points_be", se utilizará un array de tipo "MqlParam". Esta estructura es parte del lenguaje MQL5 y normalmente se emplea al añadir indicadores con la función "ChartIndicatorAdd()".

      En este caso, se utilizará para transferir valores hacia la clase. La función "Set" será sobrescrita desde la clase base. Se utiliza la palabra "override" para indicarlo.

      void               Set(MqlParam &params[]) override;

      Dentro de la función, se comprueba que el tamaño del array "params" no sea menor que dos. Si esta condición no se cumple, se mostrará un mensaje de error informativo. En caso contrario, se llamará a la función "SetSimple" para asignar los valores correspondientes.

      //+------------------------------------------------------------------+
      //| 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));
       }

      Función SetSimple

      La función "SetSimple" permite establecer directamente los valores para "points_be" y "extra_points_be" sin utilizar un array de parámetros. En este caso, se reciben los valores enteros directamente en la llamada.

      //+------------------------------------------------------------------+
      //| 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
       }

      Este método puede ser útil si los valores se tienen definidos directamente en el código o si se quiere evitar el uso del tipo "MqlParam" en algunos casos específicos.

      Función para añadir datos de las posiciones al array PositionsBe

      Para continuar con el desarrollo de la clase "CBreakEvenSimple", es necesario sobrescribir la función "Add". Esta función permite calcular y registrar los valores fundamentales de la estructura "position_be" que luego será almacenada en el array "PositionsBe".

      El parámetro "open_price" es utilizado como base para calcular dos valores: el "breakeven_price" y el "price_to_beat". Estas variables definen el nivel al cual se moverá el stop loss y el nivel que el precio debe alcanzar para activar el ajuste, respectivamente.

      Para calcular el valor de "breakeven_price" se utilizan las siguientes fórmulas:

      • Posiciones de compra:

      break_even_price = open_price + (point_value * extra_points_be)

      • Posiciones de venta:

      break_even_price = open_price - (point_value * extra_points_be)

      Por otro lado, para el cálculo del "price_to_beat" se utilizan las mismas fórmulas, pero reemplazando la variable "extra_points_be" por "points_be".

      Una vez definidos los valores anteriores, se completa la estructura "position_be" con los datos necesarios. Finalmente, esta estructura es añadida al array "PositionsBe" utilizando la función "AddArrayNoVerification", ya utilizada en la sección anterior dedicada a la gestión del riesgo.

      El código implementado queda de la siguiente forma:

      //+----------------------------------------------------------------------------------------------+
      //| 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.PostionsBe,new_pos); 
        return true;
       }

      Código completo de la clase:

      //+------------------------------------------------------------------+
      //| 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.PostionsBe, 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
       }
      //+------------------------------------------------------------------+

      Con esto, la implementación básica del breakeven por puntos fijos queda concluida. En la siguiente sección, se probará la funcionalidad de esta clase utilizando el bot de Order Blocks desarrollado en el último artículo de gestión de riesgo.


      Probamos el breakeven

      En esta sección adaptaremos el bot de Order Blocks para integrar el uso del breakeven basado en puntos fijos. El objetivo es validar su funcionamiento y comprobar el comportamiento de las posiciones con esta gestión activa.

      Para comenzar, modificamos los parámetros del bot, añadiendo una nueva sección específica:

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

      Dentro de esta sección se colocarán todos los parámetros relacionados con el breakeven. Como hasta este punto solo se ha desarrollado una variante basada en puntos fijos, se incluirá un parámetro general de tipo "bool" para activar o desactivar su uso.

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

      Seguidamente, se definirá una subsección para los parámetros específicos de esta modalidad.

      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
      

      Para integrar la funcionalidad, se crea un objeto de tipo "CBreakEvenSimple". Más adelante, este será reemplazado por un objeto más flexible del tipo "manager" que permita manejar distintas variantes desde una sola interfaz.

      CBreakEvenSimple break_even(_Symbol, Magic, BREAKEVEN_MODE_AUTOMATIC);

      En la función "OnInit", si el parámetro "use_be" está habilitado, se asignarán los valores configurados mediante la función "SetSimple".

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

      Para que la gestión de breakeven funcione correctamente, dentro de "OnTradeTransaction" se debe ejecutar la función "OnTradeTransactionEvent" del objeto "break_even". Esta se encargará de registrar o eliminar operaciones dentro del array correspondiente.

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

      Finalmente, dentro de "OnTick" se llamará a la función breakeven únicamente si el uso del sistema está habilitado por el usuario:

      if(use_be) break_even.BreakEven();   

      Con estas modificaciones, el bot ahora tiene la capacidad de ejecutar breakeven automáticamente, cuando las condiciones configuradas se cumplan. 

      Backtest

      El backtest del bot de order blocks con breakeven se realizara sin restricciones de gestión monetaria. No se configuraron límites de pérdida ni de ganancia. Las operaciones solo podrán cerrarse al alcanzar el stoploss o el takeprofit. Se utilizó una relación "Risk:Reward" de "1:2", donde el takeprofit es el doble del stoploss.

      1. Configuración general:

          Imagen 4: Configuraciones generales para los backtest

      2. Gráfico del backtest para una operativa sin breakeven.

      Imagen 5: Gráfico del backtest sin el uso del breakeven

      La imagen 5 muestra el comportamiento del bot sin el uso del breakeven. Durante esta prueba, el balance inicial de 15,000 usd disminuyó hasta 8,700 usd, generando una pérdida de aproximadamente 7,000 usd. Este resultado sirve como referencia para evaluar los efectos de activar o no, esta función.

      3. Gráfico del backtest para una operativa con breakeven.

      Imagen 6: Gráfico del backtest con el uso del breakeven

      Se realizó una segunda prueba con el breakeven activado. Se estableció el parámetro "be_fixed_points_extra" en 100 puntos y el parámetro "be_fixed_points_to_put_be" en 600 puntos.

      Durante esta evaluación, el gráfico mostró un desarrollo más lento. En secuencias de operaciones perdedoras, la caída del balance fue menor. El saldo pasó de 10,900 usd a 7,900 usd, lo que representa una pérdida aproximada de 4,000 usd. Esta diferencia de 3,000 usd, en comparación con el primer escenario, estos resultados indican que el uso del breakeven puede ayudar a reducir el impacto durante rachas negativas.

      No obstante, también se presentaron limitaciones. En ambas pruebas, las operaciones comenzaron con pérdidas hasta aproximadamente marzo de 2024. A partir del 2024.03.01, se observó un cambio, apareciendo una serie de operaciones ganadoras. En el primer backtest, el balance superó los 16,000 usd, mientras que en el segundo, con breakeven activado, solo alcanzó los 10,900 usd.

      Una posible explicación está en los cierres anticipados generados por el breakeven. Si el precio retrocede después de abrir una posición, pero no llega al stoploss, en un entorno sin breakeven puede regresar a la dirección esperada y cerrar con ganancia. En cambio, con breakeven activo, la posición se cerraría en equilibrio, eliminando la posibilidad de capturar ese beneficio.

      Esto sugiere que en el primer caso muchas operaciones permanecieron en flotante negativo antes de alcanzar el takeprofit. Por esta razón, al aplicar un breakeven basado en puntos fijos, el resultado puede verse limitado en escenarios donde los retrocesos de precio son frecuentes.

      En condiciones de alta volatilidad, esta configuración puede activarse rápidamente, protegiendo el capital, pero también cerrando operaciones con anticipación. En situaciones de menor movimiento, puede no llegar a activarse. Por lo tanto, el uso de breakeven por puntos fijos puede ajustarse mejor a operadores que priorizan la protección del saldo frente a la obtención de un rendimiento mayor por operación.


      Conclusión

      En este artículo se analizó el concepto de breakeven, su implementación en MQL5 y algunas variantes posibles. Todo lo desarrollado se aplicó en el bot de Order Blocks, creado en él último artículo de gestión de riesgo.

      Al final del análisis, se comparó el comportamiento con y sin la activación del breakeven. Se observó que, en secuencias de ganancias, su uso puede limitar el crecimiento del balance. En cambio, durante rachas negativas, reduce la exposición a pérdidas amplias. Esta dualidad sugiere un punto de balance, aunque su eficacia real dependerá de los resultados estadísticos de la estrategia base, sin restricciones externas (como límites de ganancias o pérdidas).

      También se concluyó que el uso del breakeven puede adaptarse mejor a perfiles de operadores que no buscan una ejecución agresiva, sino una progresión constante en sus resultados, con mayor control sobre las salidas.

      Archivos usados/mejorados en este artículo:

      Nombre del archivo Tipo Descripción 
       Risk_Management.mqh  .mqh (archivo de cabecera) Contiene la clase de gestión de riesgo desarrollada en el en él último artículo de gestión de riesgo.
       Order_Block_Indicador_New_Part_2.mq5 .mq5 (indicador) Contiene el código del indicador de order blocks.
       Order Block EA MetaTrader 5.mq5  .mq5 (asesor experto) Código del bot de Order Block con el Breakeven integrado.
       OB_SET_WITHOUT_BREAKEVEN.set .set (archivo de configuración) Configuraciones del primer backtest, sin breakeven.
       OB_SET_WITH_BREAKEVEN.set .set (archivo de configuración) Configuraciones del segundo backtest, con breakeven activo.
       PositionManagement.mqh .mqh (archivo de cabecera) Archivo mqh que contiene el código del breakeven.
      Archivos adjuntos |
      MQL5.zip (210.17 KB)
      Del básico al intermedio: Definiciones (I) Del básico al intermedio: Definiciones (I)
      En este artículo, haremos cosas que para muchos parecerán extrañas y totalmente fuera de contexto, pero que, si se aplican bien, harán que tu aprendizaje sea mucho más divertido y emocionante, ya que podemos construir cosas bastante interesantes basándonos en lo que se muestra aquí, lo que permite una mejor asimilación de la sintaxis del lenguaje MQL5. El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe considerarse una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos mostrados.
      Optimización del modelo de nubes atmosféricas — Atmosphere Clouds Model Optimization (ACMO): Teoría Optimización del modelo de nubes atmosféricas — Atmosphere Clouds Model Optimization (ACMO): Teoría
      Este artículo se centra en el algoritmo metaheurístico Atmosphere Clouds Model Optimisation (ACMO), que modela el comportamiento de las nubes para resolver problemas de optimización. El algoritmo usa los principios de generación, movimiento y propagación de nubes, adaptándose a las "condiciones meteorológicas" del espacio de soluciones. El artículo revela cómo una simulación meteorológica del algoritmo encuentra soluciones óptimas en un espacio de posibilidades complejo y detalla las etapas del ACMO, incluida la preparación del "cielo", el nacimiento de las nubes, su movimiento y la concentración de la lluvia.
      Obtenga una ventaja sobre cualquier mercado (Parte IV): Índices CBOE de volatilidad del euro y el oro Obtenga una ventaja sobre cualquier mercado (Parte IV): Índices CBOE de volatilidad del euro y el oro
      Analizaremos datos alternativos curados por el 'Chicago Board Of Options Exchange' (CBOE) para mejorar la precisión de nuestras redes neuronales profundas al pronosticar el símbolo XAUEUR (oro).
      Del básico al intermedio: Recursividad Del básico al intermedio: Recursividad
      En este artículo, veremos un concepto de programación muy interesante y bastante divertido, aunque debe ser tratado con extremo respeto, ya que un mal uso o un mal entendimiento del mismo convierte programas relativamente simples en algo innecesariamente complicado. Aunque, el buen uso y la perfecta adecuación en situaciones igualmente adecuadas convierten la recursividad en un gran aliado para resolver cuestiones que, de otra forma, serían mucho más trabajosas y demoradas. El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe ser considerado como una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos mostrados.