Español Português
preview
Реализация механизма безубыточности в MQL5 (Часть 1): Базовый класс и режим безубытка по фиксированным пунктам

Реализация механизма безубыточности в MQL5 (Часть 1): Базовый класс и режим безубытка по фиксированным пунктам

MetaTrader 5Примеры |
755 0
Niquel Mendoza
Niquel Mendoza


Введение

Перевод Stop Loss в безубыток (breakeven) — это техника, используемая в трейдинге для более безопасного управления открытыми позициями. Она заключается в перемещении уровня Stop Loss на цену открытия сделки после того, как та прошла определенное количество пунктов в плюс. Так можно защитить позицию и уменьшить убыток при неожиданном откате.

Применение механизма безубыточности включает два основных подхода:

  • позволяет гарантировать, что если цена не достигнет целевого уровня прибыли Take Profit, сделка не завершится с убытком;
  • используется для фиксации прибыли путем установки Stop Loss на несколько пунктов выше цены входа.

В этой серии статей мы разработаем три варианта реализации механизма безубыточности. А сейчас, в первой части, создадим базовый класс системы и запрограммируем первый простой тип безубытка, который послужит шаблоном для будущих расширений.


Что такое режим безубытка

Прежде чем интегрировать концепцию безубыточности в нашу систему, важно понимать базовый механизм ее работы.

В упрощенном виде режим безубытка подразумевает перемещение уровня Stop Loss на фиксированное количество пунктов (дополнительные пункты) после того, как цена сместилась на определенное расстояние от цены открытия сделки.


    be1

    Рисунок 1. Позиция на продажу до модификации уровня Stop Loss

    На первом рисунке видно, что Stop Loss изначально располагается на уровне сигнала, генерируемого индикатором Order Blocks (индикатор, разработанный в предыдущих статьях другой серии).

     be2

    Рисунок 2. Позиция на продажу после модификации уровня Stop Loss

    На рисунке 2 показано, как после появления новой свечи система автоматически перемещает уровень Stop Loss на заданное расстояние, соответствующее установленным 150 пунктам. Это поведение отражает процесс активации, который был описан выше.

    Далее мы рассмотрим, что произошло после активации режима безубытка.

    be3


    Рисунок 3: Позиция на продажу после закрытия на уровне безубыточности

    Как можно увидеть на рисунке 3, позиция закрылась на уровне безубыточности с небольшой прибылью за счет ранее настроенной корректировки. В данном конкретном случае удалось избежать полного убытка (Stop Loss), поскольку цена не прошла значительного расстояния от точки входа.

    Это одно из преимуществ механизма безубыточности: он позволяет обеспечить потенциальную прибыль или избежать тотальных убытков.
    Однако важно отметить, что обеспечение прибыли зависит от того, на сколько дополнительных пунктов установлен уровень безубытка.

    С другой стороны, существуют и некоторые недостатки применения этого метода. Иногда, если бы  Stop Loss не был перемещен, сделка могла бы достичь целевого уровня прибыли (Take Profit). Эта ситуация во многом зависит от используемой стратегии.

    В нашем случае, при работе с сигналами Order Blocks, применение режима безубытка обычно целесообразно, поскольку бывают случаи, когда блоки ордеров не отрабатываются, и защита открытых позиций становится критически важной.

    Режим безубытка на основе ATR

    Другой вариант управления — это динамический перевод Stop Loss в безубыток на основе ATR (Average True Range).

    Этот метод похож на предыдущий, но вместо использования фиксированного количества пунктов используется параметр multiplier. Multiplier позволяет рассчитать оптимальное расстояние для корректировки Stop Loss с учетом текущей волатильности рынка. Такой подход оказывается более гибким при работе с высоковолатильными активами, такими как золото.

    Например, если настроен фиксированный уровень безубытка в 150 пунктов (эквивалент 1,50 USD в золоте), то в периоды, предшествующие экономическим новостям, цена на золото может легко подняться до 3,00 USD и выше. В таких ситуациях использование динамической корректировки на основе ATR помогает лучше адаптироваться к поведению рынка, не позволяя обычным колебаниям преждевременно закрывать сделку.

    Таким образом, метод, основанный на ATR, не использует фиксированные расстояния, а динамически адаптируется к текущим условиям.

    Режим безубытка на основе соотношения Risk-Reward Ratio (RRR)

    Наконец, можно переводить Stop Loss в безубыток на основании соотношения риска и доходности (Risk-Reward Ratio, RRR). RRR представляет собой соотношение между принятым риском и ожидаемой прибылью от сделки. Рассчитывается путем деления значения Take Profit на значение Stop Loss.

    Понимание RRR важно для более качественного управления позициями:

    • когда RRR выше, процент успешных сделок обычно снижается, поскольку цене необходимо пройти большее расстояние, чтобы достичь цели;
    • когда RRR ниже, вероятность достижения Take Profit увеличивается, но потенциальные убытки могут быть выше, если рынок движется против.

      Логика применения режима безубытка на основе RRR проста. Предположим, позиция открыта с Take Profit, в два раза превышающим Stop Loss, то есть с RRR 1:2.

      При использовании этого метода Stop Loss можно настроить так, чтобы он перемещался к цене входа, когда сделка достигнет соотношения 1:1 относительно первоначального риска. Это означает, что как только рынок достаточно продвинется, чтобы уравновесить принятый риск, сделка будет автоматически защищена.

      Например:

      • при достижении соотношения 1:1 Stop Loss можно переместить, чтобы защитить позицию;
      • его также можно настроить на дальнейшее продвижение вперед, например, в соотношении 1:2, 1:3 и т. д., в зависимости от типа используемой стратегии.

        Такой подход позволяет защищать сделки после того, как рынок продемонстрировал достаточное движение в благоприятном направлении, исходя из определенного соотношения риска и доходности.


        Необходимые структуры и перечисления

        В этом разделе мы начнем программировать основы механизма безубыточности. Перед созданием основного класса определим необходимые структуры и перечисления. Процесс будет понятным, простым и прямолинейным. Мы не будем использовать все перечисления, созданные нами для управления рисками, поскольку для реализации безубытка нам понадобятся лишь некоторые из них. Давайте начнем.

        Мы создадим простую структуру для хранения основной информации, которая позволит применять режим безубытка. Эта структура должна содержать:

        • ticket сделки,
        • price_to_beat, то есть цену, которую рынок должен превысить, чтобы переместить Stop Loss на breakeven_price,
        • breakeven_price, который станет новым уровнем, на котором будет установлен Stop Loss после выполнения условия,
        • type операции для корректного определения уровней.

          Структура будет выглядеть следующим образом:

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

          Перечисление для типа безубыточности

          Мы создали перечисление, которое позволяет выбрать один из трех типов активации режима безубытка, упомянутых ранее, разработанных для оптимизации управления каждой сделкой:

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

          Перечисление для режима безубытка

          Не все торговые стратегии работают одинаково. Поэтому мы создали перечисление, которое позволяет однозначно выбрать, как именно будет применяться режим безубытка к открытым сделкам.

          enum ENUM_BREAKEVEN_MODE
           {
            BREAKEVEN_MODE_AUTOMATIC,
            BREAKEVEN_MODE_MANUAL
           };

          • Режим BREAKEVEN_MODE_AUTOMATIC автоматически фиксирует все новые сделки с помощью OnTradeTransaction. Трейдеру не нужно делать ничего дополнительного. Система мгновенно обнаруживает, защищает и управляет сделками.
          • Режим BREAKEVEN_MODE_MANUAL обеспечивает максимальную гибкость настройки. Трейдер выбирает, какие тикеты (tickets) будут защищены механизмом безубыточности и в какой момент начнется пересмотр условий. Этот режим идеально подходит для стратегий, требующих ручного отбора, таких как дискретный скальпинг или высокоточные торговые сетапы.


          Создание базового класса CBreakEvenBase

          Прежде чем приступить к определению базового класса, мы подключим управление рисками, разработанное в предыдущих статьях.

          #include <Risk_Management.mqh>

          Защищенные переменные CBreakEvenBase

          Ниже представлено определение класса и его внутренних переменных. Для каждой переменной также приводится пояснение.

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

          Переменная, которая отправляет и модифицирует ордера. 

          CTrade             obj_trade;    //CTrade object

          Структура MqlTick, в которой будет храниться информация о последнем записанном тике. 

          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;

          Функции CBreakEvenBase

          В этом разделе мы начинаем определять функции, которые будут наследоваться различными классами, реализованными далее.

          Конструктор

          Конструктор является ключевой частью любого класса. В классе CBreakEvenBase его задача — инициализировать внутренние переменные и присвоить им необходимые значения, чтобы класс функционировал корректно.

          Этот конструктор принимает три параметра: symbol, по которому будут браться данные bid и ask; magic, позволяющий идентифицировать конкретные ордера, и mode, который указывает выбранный тип управления безубыточностью (может быть автоматическим или ручным).

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

          Определение конструктора

          При выполнении конструктора переменные pause и allow_extra_logs инициализируются значением false. Затем проверяется, является ли полученное значение mode допустимым. Если это не так, выводится сообщение об ошибке и вызывается ExpertRemove для удаления советника с графика, чтобы предотвратить продолжение выполнения кода с некорректной настройкой.

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

          Соответствующие значения также присваиваются параметрам symbol, magic и breakeven_mode с использованием значений, полученных в качестве аргументов.

          Значение пункта символа сохраняется в переменной point_value, а количество параметров инициализируется значением «0». Каждый класс, наследуемый от CBreakEvenBase, будет определять собственное количество параметров в своем собственном конструкторе.

          Кроме того, если переменная magic_ равна NOT_MAGIC_NUMBER, мы не будем выполнять функцию-член класса CTrade, предназначенную для установки магического номера. 

          Примечание: NOT_MAGIC_NUMBER  это define, созданный внутри include-файла управления рисками. Этот define служит для обозначения того, что магический номер не будет использоваться, поэтому в случае использования механизма безубыточности он будет применяться ко всем сделкам (только если режим безубытка является автоматическим).

          Деструктор

          Деструктор используется для освобождения памяти, используемой массивом, в котором хранятся управляемые позиции. Эта функция выполняется автоматически при уничтожении объекта.

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


          Реализация общих функций класса

          Далее мы определим функции, которые являются частью базового класса CBreakEvenBase. Эти функции будут использоваться и расширяться производными классами, которые будут реализованы позже.

          Для начала объявим функцию, которая возвращает количество параметров, необходимых классу. Эта функция просто возвращает значение защищенной переменной num_params. Кроме того, она помечена как const, чтобы предотвратить изменение состояния объекта, и как final, чтобы запретить ее переопределение дочерними классами.

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

          Далее мы объявляем функцию Add, которая будет использоваться для добавления новой структуры position_be в массив PositionsBe. Эта функция будет получать необходимые данные сделки, такие как ticket, цена открытия, Stop Loss и тип позиции.

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

          Каждый класс, наследующий от CBreakEvenBase, должен реализовать свою собственную версию этой функции, поскольку она объявлена как чистая и виртуальная.

          Основная функция этого класса — обеспечение безубыточности. Она отвечает за применение корректировки безубытка ко всем позициям, хранящимся в массиве PositionsBe. Для этого массив перебирается с помощью цикла for.

          Перед применением корректировки проверяется, что размер массива больше нуля, и что переменная pause имеет значение false. Если эти условия не выполняются, функция немедленно завершает работу.

          Поскольку структура position_be уже содержит как целевой уровень, так и цену, при которой должна быть инициирована корректировка, необходимо лишь проверить, выполняется ли рыночное условие:

          • если позиция является покупкой, проверяется, что цена ask больше или равна значению price_to_beat;
          • если это продажа, проверяется, что цена bid меньше или равна этому же значению.

          После подтверждения условия, позиция определяется с помощью соответствующего тикета, и рассчитывается текущее значение Take Profit. Затем выполняется модификация с помощью метода PositionModify класса CTrade. В конце эта позиция помечается для удаления из массива.

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

          Чтобы добавить структуру в массив PositionsBe при открытии новой сделки, мы определяем функцию, которая будет вызываться внутри события OnTradeTransaction. Эта функция называется OnTradeTransactionEvent и будет выполняться каждый раз при совершении торговой транзакции.

          virtual void       OnTradeTransactionEvent(const MqlTradeTransaction& trans) final;

          Перед добавлением позиции в массив PositionsBe необходимо выбрать соответствующий ей ticket с помощью функции HistoryDealSelect. Затем определяется тип входа в сделку с помощью значения entry.

          Если entry соответствует входу в рынок, то есть DEAL_ENTRY_IN, проверяется, что сделка полностью открыта, что режим безубытка автоматический — BREAKEVEN_MODE_AUTOMATIC, и что магический номер сделки совпадает с внутренней переменной magic, либо что последняя имеет значение NOT_MAGIC_NUMBER.

          Когда все условия выполняются, позиция добавляется в массив PositionsBe с помощью функции Add. Если allow_extra_logs активирована, выводится сообщение с указанием регистрации позиции.

          В случае, когда entry соответствует выходу, то есть DEAL_ENTRY_OUT, и позиция полностью закрыта, соответствующий ticket удаляется из массива PositionsBe. Во избежание потенциальных конфликтов при этом удалении, переменной pause временно присваивается значение true.

          Код функции структурирован следующим образом:

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

          Некоторым производным классам потребуется настройка различных параметров. Например, простая корректировка нуждается в двух целочисленных значениях, тогда как другие методы, такие как метод на основе ATR, требуют таких данных, как период, таймфрейм и multiplier.

          Поскольку в базовом классе невозможно определить несколько вариантов одной виртуальной функции с разными параметрами, предлагается использовать массив типа MqlParam для хранения необходимой конфигурации. Таким образом, каждый наследующий класс сможет внутренне интерпретировать и присваивать полученные значения.

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

          Наконец, добавляется функция, позволяющая активировать или отключать дополнительные информационные сообщения. Это полезно, когда необходимо регистрировать такие действия, как добавление или удаление тикетов во время выполнения программы.

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


          Разработка первого класса CBreakEvenSimple

          Внутренние переменные

          Теперь, когда базовый класс готов, мы начнем разработку класса CBreakEvenSimple. Этот класс применяет метод безубытка, используя фиксированное количество пунктов.

          Для этого объявляются две переменные:

          int                extra_points_be, points_be;

          • переменная extra_points_be обозначает дополнительное расстояние, на которое будет перемещен уровень безубыточности после активации,
          • переменная points_be определяет, сколько пунктов в плюс должна пройти цена, прежде чем будет выполнена корректировка.

          Конструктор

          В конструкторе необходимо установить начальные значения. Также вызывается конструктор базового класса для присвоения унаследованных параметров. Внутри тела конструктора внутренние переменные инициализируются нулем, и определяется количество параметров, необходимых классу, которых в данном случае — два.

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

          Функция для установки параметров

          Для присвоения значений внутренним переменным extra_points_be и points_be будет использоваться массив типа MqlParam. Эта структура является частью языка MQL5 и обычно применяется при добавлении индикаторов через функцию ChartIndicatorAdd().

          В данном случае она будет использоваться для передачи значений классу. Функция Set будет переопределена относительно базового класса. Для этого используется ключевое слово "override".

          void               Set(MqlParam &params[]) override;

          Внутри функции проверяется, что размер массива params не меньше двух. Если это условие не выполняется, отобразится информационное сообщение об ошибке. В противном случае, будет вызвана функция SetSimple для присвоения соответствующих значений.

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

          Функция SetSimple

          Функция SetSimple позволяет напрямую задавать значения для points_be и extra_points_be без использования массива параметров. В этом случае целочисленные значения принимаются непосредственно при вызове функции.

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

          Этот метод может быть полезен, если значения определены непосредственно в коде, или если в некоторых специфических случаях требуется избежать использования типа MqlParam.

          Функция для добавления данных позиций в массив PositionsBe

          Для продолжения разработки класса CBreakEvenSimple необходимо переопределить функцию Add. Эта функция позволяет вычислить и зарегистрировать основные значения структуры position_be, которая затем будет сохранена в массиве PositionsBe.

          Параметр open_price используется в качестве основы для вычисления двух значений: breakeven_price и price_to_beat. Эти переменные определяют уровень, на который будет перемещен Stop Loss, и уровень, которого должна достичь цена, чтобы активировалась корректировка.

          Для вычисления значения breakeven_price используются следующие формулы:

          • Позиции на покупку:

          break_even_price = open_price + (point_value * extra_points_be)

          • Позиции на продажу:

          break_even_price = open_price - (point_value * extra_points_be)

          С другой стороны, для расчета price_to_beat используются те же формулы, но переменная extra_points_be заменяется на points_be.

          После определения указанных значений, структура position_be заполняется необходимыми данными. Затем эта структура добавляется в массив PositionsBe с помощью функции AddArrayNoVerification, которая уже использовалась в предыдущем разделе, посвященном управлению рисками.

          Код реализации выглядит следующим образом:

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

          Полный код класса:

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

          На этом базовая реализация метода безубыточности по фиксированным пунктам завершается. В следующем разделе функциональность этого класса будет протестирована с помощью советника Order Blocks, разработанного в последней статье по управлению рисками.


          Тестирование режима безубытка

          В этом разделе мы адаптируем советника Order Blocks, чтобы интегрировать применение режима безубытка по фиксированным пунктам. Цель состоит в том, чтобы проверить работоспособность системы и оценить поведение позиций при таком активном управлении.

          Для начала мы изменяем параметры советника, добавляя новый специализированный раздел:

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

          В этом разделе будут размещены все параметры, относящиеся к механизму безубыточности. Поскольку до настоящего момента разрабатывался только вариант по фиксированным пунктам, добавляется общий параметр типа bool для его активации или деактивации.

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

          Далее будет определен подраздел для специфических параметров данного метода.

          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
          

          Для интеграции функциональности создается объект типа CBreakEvenSimple. В дальнейшем он будет заменен более гибким объектом типа manager, который позволит управлять различными вариантами из единого интерфейса.

          CBreakEvenSimple break_even(_Symbol, Magic, BREAKEVEN_MODE_AUTOMATIC);

          В функции OnInit, если параметр use_be включен, будут присвоены значения, настроенные с помощью функции SetSimple.

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

          Чтобы управление безубытком работало корректно, необходимо, чтобы функция OnTradeTransactionEvent объекта break_even выполнялась внутри функции OnTradeTransaction. Она будет отвечать за регистрацию или удаление сделок в соответствующем массиве.

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

          Наконец, внутри OnTick функция безубытка будет вызываться только в том случае, если использование этого механизма включено пользователем:

          if(use_be) break_even.BreakEven();   

          Благодаря этим изменениям, робот теперь может автоматически применять механизм безубыточности, когда выполняются заданные условия. 

          Backtest

          Бэктест советника Order Blocks с режимом безубытка был проведен без ограничений по управлению капиталом. Не были установлены лимиты ни по прибыли ни по убыткам. Сделки могли закрываться только при достижении уровня Stop Loss или Take Profit. Использовалось соотношение Risk:Reward 1:2, где Take Profit вдвое превышает Stop Loss.

          1. Общие настройки:

              Рисунок 4. Общие настройки для бэктеста

          2. График бэктеста работы стратегии без механизма безубыточности.

          Рисунок 5. График бэктеста без использования механизма безубыточности

          На рисунке 5 показано поведение советника без использования механизма безубыточности. В ходе этого теста первоначальный баланс в 15 000 USD уменьшился до 8 700 USD, что привело к убытку в размере приблизительно 7 000 USD. Этот результат служит ориентиром для оценки последствий включения или отключения этой функции.

          3. График бэктеста работы стратегии с механизмом безубыточности.

          Рисунок 6. График бэктеста с использованием механизма безубыточности

          Второй тест был проведен с активированным режимом безубытка. Параметр be_fixed_points_extra был установлен на 100 пунктов, а параметр be_fixed_points_to_put_be — на 600 пунктов.

          Во время этого тестирования график показал более медленное развитие. В сериях убыточных сделок снижение баланса было меньшим. Баланс снизился с 10 900 USD до 7 900 USD, что составляет 3 000 USD убытка. Эта разница в 3 000 USD по сравнению с первым сценарием указывает на то, что использование механизма безубыточности может помочь уменьшить влияние убыточных серий.

          Однако были выявлены и ограничения. В обоих тестах сделки начинались с убытков примерно до марта 2024 года. Начиная с 01.03.2024, наблюдались изменения: появилась серия прибыльных сделок. В первом бэктесте баланс превысил 16 000 USD, тогда как во втором, с активированным режимом безубытка, он достиг лишь 10 900 USD.

          Одним из возможных объяснений являются ранние закрытия, вызванные режимом безубытка. Если после открытия позиции цена откатывается, но не достигает уровня Stop Loss, то в условиях отсутствия механизма безубыточности она может вернуться в ожидаемом направлении и закрыться с прибылью. Напротив, при активированном режиме безубытка позиция закроется в ноль, что исключает возможность получить эту прибыль.

          Это позволяет предположить, что в первом случае многие сделки оставались в отрицательном балансе до достижения Take Profit. По этой причине при применении режима безубытка по фиксированным пунктам, результат может быть ограничен в сценариях, где ценовые откаты происходят часто.

          В условиях высокой волатильности данная конфигурация может быстро активироваться, защищая капитал, но одновременно преждевременно закрывая сделки. В периоды меньшей активности она может не срабатывать. Таким образом, использование режима безубытка по фиксированным пунктам больше подойдет трейдерам, которые отдают приоритет защите баланса, а не получению более высокой прибыли на сделку.


          Заключение

          В этой статье мы проанализировали концепцию безубыточности, ее реализацию в MQL5 и некоторые возможные варианты. Все разработанное было применено в советнике Order Blocks, созданном в последней статье по управлению рисками.

          В завершение анализа было проведено сравнение поведения системы с активированным режимом безубытка и без него. Было замечено, что в сериях прибыльных сделок его использование может ограничивать рост баланса. И наоборот, во время убыточных серий режим безубытка снижает экспозицию к крупным убыткам. Эта двойственность указывает на существование некоего баланса между риском и результатом, хотя реальная эффективность будет зависеть от статистических результатов базовой стратегии без внешних ограничений (таких как лимиты прибыли или убытков).

          Также мы пришли к заключению, что использование режима безубытка больше подойдет тем трейдерам, которые не стремятся к агрессивной торговле, а предпочитают устойчивое развитие результатов с большим контролем над выходами из сделок.

          Файлы, использованные/обновленные в этой статье:

          Имя файла Тип Описание 
           Risk_Management.mqh  .mqh (заголовочный файл) Содержит класс управления риском, разработанный в последней статье серии по управлению рисками.
           Order_Block_Indicador_New_Part_2.mq5 .mq5 (индикатор) Содержит код индикатора Order Block.
           Order Block EA MetaTrader 5.mq5  .mq5 (советник) Код робота Order Block с интегрированным режимом безубытка.
           OB_SET_WITHOUT_BREAKEVEN.set .set (файл конфигурации) Настройки первого бэктеста, без механизма безубыточности.
           OB_SET_WITH_BREAKEVEN.set .set (файл конфигурации) Настройки второго бэктеста с активированным режимом безубытка.
           PositionManagement.mqh .mqh (заголовочный файл) Файл mqh, содержащий код механизма безубыточности.


          Перевод с испанского произведен MetaQuotes Ltd.
          Оригинальная статья: https://www.mql5.com/es/articles/17957

          Прикрепленные файлы |
          MQL5.zip (210.17 KB)
          Объединяем LLM, CatBoost и квантовые вычисления в единую торговую систему Объединяем LLM, CatBoost и квантовые вычисления в единую торговую систему
          В статье предлагается синтез новых технологий для преодоления ограничений классических индикаторов в аналитике рыночных данных. Показано, как языковые модели и квантовое кодирование могут выявлять скрытые рыночные паттерны, которые традиционные методики упускают. Эксперимент подтверждает ценность новых технологий и предлагает обновлённую методологию анализа, соответствующую современному уровню вычислительных инноваций.
          Алгоритм оптимизации одуванчика — Dandelion Optimizer (DO) Алгоритм оптимизации одуванчика — Dandelion Optimizer (DO)
          Алгоритм оптимизации одуванчика DO превращает простой полёт семени по ветру в стратегию математического поиска. Три фазы - вихревой подъём, дрейф к центру популяции и приземление по траектории Леви - формируют изящную метафору, которая на практике показывает интересные результаты.
          Нейросети в трейдинге: Сеточная аппроксимация событийного потока как инструмент анализа ценовых паттернов (MDC-модуль) Нейросети в трейдинге: Сеточная аппроксимация событийного потока как инструмент анализа ценовых паттернов (MDC-модуль)
          Представляем реализацию ключевых компонентов фреймворка EEMFlow средствами MQL5. Статья демонстрирует, как многомасштабная обработка событий, спайковые модули FAM и адаптивное объединение признаков в MDC формируют структурированное и адаптированное к плотности рынка представление. Это позволяет стратегии эффективно выявлять значимые сигналы, сочетать микроимпульсы с глобальными тенденциями и повышать точность прогнозов, обеспечивая трейдеру надежный инструмент для анализа и принятия решений.
          Разработка инструментария для анализа движения цен (Часть 17): Советник TrendLoom Разработка инструментария для анализа движения цен (Часть 17): Советник TrendLoom
          Как ценовой аналитик и трейдер, я заметил, что когда тренд подтверждается на нескольких таймфреймах, он обычно продолжается в этом направлении. Продолжительность тренда может варьироваться в зависимости от стратегии трейдера: держит ли он позиции на долгосрочную перспективу или занимается скальпингом. Выбранные вами таймфреймы играют решающую роль. Статья знакомит с быстрой автоматизированной системой, которая помогает увидеть общий тренд сквозь разные тймфреймы всего одним нажатием кнопки или с помощью регулярных обновлений.