Реализация механизма безубыточности в MQL5 (Часть 2): Безубыток на основе ATR и RRR
- Введение
- Разработка класса CBreakEvenAtr на основе индикатора ATR
- Основы и проектирование класса CBreakEvenRR на основе RRR
- Класс CBreakEven: выбор и динамическая настройка режима безубытка
- Тестирование советника Order Blocks с различными типами безубытка
- Заключение
Введение
Приветствую всех во второй части статьи о механизме безубыточности. Здесь мы продолжим программирование различных типов безубытка, реализация которых осталась незавершенной в первой части. Кроме того, мы создадим дополнительный класс, упрощающий выбор режима безубытка со стороны пользователя. Сначала мы рассмотрим тип безубытка на основe ATR, затем — связанный с RRR, и в завершение — класс для использования механизма безубыточности. В конце мы проведем сравнительный анализ, чтобы оценить, какой тип безубытка является наиболее подходящим для советника Order Blocks, изначально разработанного в последней статье серии по управлению рисками.
Разработка класса CBreakEvenAtr на основе индикатора ATR
Режим безубытка на основе ATR, как уже упоминалось в предыдущей статье, заключается в том, что фиксированные пункты не используются ни для расстояния, на котором активируется безубыток, или для разницы между ценой безубытка и ценой открытия позиции. Вместо этого применяются множители, накладывающиеся на значение ATR. Такой подход обеспечивает большую гибкость в настройке расстояния до активации безубытка и точки, в которой он будет активирован.
Высокое значение ATR-множителя обычно означает больший отступ, поэтому безубыток будет срабатывать реже. И напротив, более низкое значение может привести к тому, что безубыток будет срабатывать чаще обычного, что может негативно повлиять на получение прибыли, если дать позиции развиться.
Для реализации этого подхода в MQL5 мы создадим класс CBreakEvenAtr, который будет наследоваться от CBreakEvenBase — базового класса для любого типа безубытка.
//--- class CBreakEvenAtr class CBreakEvenAtr : public CBreakEvenBase
Для работы с ATR нам понадобится дескриптор, который позволит нам получать данные индикатора, то есть копировать его значения. Кроме того, мы будем использовать массив типа double, под названием atr_buff, в котором будут храниться данные, скопированные с индикатора.
Также понадобится целочисленная переменная atr_idx, указывающая индекс, с которого будут копироваться данные; например, значение 0 означает, что копирование будет выполняться с текущего значения ATR. При каждом обращении будет копироваться ровно один элемент буфера.
Далее нам понадобятся два множителя: один — для расстояния, на котором будет установлен уровень безубытка и другой — для расстояния, на котором он будет активирован.
Приватные переменные
private: int atr_handle; double atr_buff[]; int atr_idx; double atr_multiplier_be; double atr_multiplier_extra_be;
Конструктор и деструктор
В конструкторе мы инициализируем приватные переменные значениями по умолчанию. Кроме того, мы подготовим массив atr_buff, который будет хранить значение ATR. Также мы определим, что классу требуется пять параметров, поэтому переменная num_params будет иметь соответствующее значение.
Конструктор будет принимать три параметра, которые передаются базовому классу CBreakEvenBase для его инициализации.
CBreakEvenAtr(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_) : CBreakEvenBase(symbol_, magic_, mode_), atr_handle(INVALID_HANDLE), atr_idx(0), atr_multiplier_be(1.0), atr_multiplier_extra_be(1.0) { ArraySetAsSeries(atr_buff, true); this.num_params = 5; }
В деструкторе будет освобожден дескриптор индикатора ATR, а также массив, в котором хранятся значения ATR:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CBreakEvenAtr::~CBreakEvenAtr(void) { ArrayFree(atr_buff); if(this.atr_handle != INVALID_HANDLE) IndicatorRelease(this.atr_handle); }
Функции для установки параметров
Продолжая определение и объявление класса для режима безубытка по ATR, пришло время определить методы для установки внутренних переменных. Для начала определим функции SetSimple. Они используются для установки параметров, необходимых классу, без использования MqlParams, что идеально для случаев, когда уже известно, что режим безубытка всегда будет основан на ATR. В рамках этих функций предусмотрены две версии:
void SetSimple(int atr_handle_, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_); void SetSimple(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe);
В первой функции SetSimple мы сначала проверим, что такие параметры, как множители, являются допустимыми.
if(atr_multiplier_extra_be_ >= atr_multiplier_be_) { printf("%s: Error | The multiplier of the atr to calculate the price be is greater than or equal to the multiplier to set the be", __FUNCSIG__); ExpertRemove(); }
Проверяем, что множитель, при котором будет активирован безубыток, меньше множителя, на котором безубыток будет установлен. Это необходимо для предотвращения таких ошибок, как недействительные уровни стопов.
Затем убеждаемся, что индекс ATR всегда больше или равен нулю.
if(atr_idx_ < 0) { printf("%s: Critical error | Atr index is less than 0", __FUNCTION__); ExpertRemove(); }
Наконец, мы убеждаемся, что дескриптор индикатора ATR не является недействительным; если это так, советник будет удален с графика.
if(atr_handle_ == INVALID_HANDLE) { printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError()); ExpertRemove(); }
После выполнения всех проверок, мы присваиваем параметры внутренним переменным класса.
this.atr_idx = atr_idx_; this.atr_multiplier_be = atr_multiplier_be_; this.atr_multiplier_extra_be = atr_multiplier_extra_be_; this.atr_handle = atr_handle_;
Полный код
//+------------------------------------------------------------------------------------+ //| Function to set the values of the CBreakEvenAtr class without using MqlParams | //| Using the handle instead of period and timeframe | //+------------------------------------------------------------------------------------+ void CBreakEvenAtr::SetSimple(int atr_handle_, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_) { if(atr_multiplier_extra_be_ >= atr_multiplier_be_) { printf("%s: Error | The multiplier of the atr to calculate the price be is greater than or equal to the multiplier to set the be", __FUNCSIG__); ExpertRemove(); } if(atr_idx_ < 0) { printf("%s: Critical error | Atr index is less than 0", __FUNCTION__); ExpertRemove(); } if(atr_handle_ == INVALID_HANDLE) { printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError()); ExpertRemove(); } this.atr_idx = atr_idx_; this.atr_multiplier_be = atr_multiplier_be_; this.atr_multiplier_extra_be = atr_multiplier_extra_be_; this.atr_handle = atr_handle_; }
Вторая функция SetSimple требует дополнительного параметра. Вместо получения дескриптора, функция запрашивает период ATR, поскольку мы будем настраивать дескриптор внутри этой функции. Затем вызывается первая функция SetSimple, которой передается созданный дескриптор.
//+------------------------------------------------------------------------------------+ //| Function to set the values of the CBreakEvenAtr class without using MqlParams | //| Configuring the handle with the period and timeframe. | //+------------------------------------------------------------------------------------+ void CBreakEvenAtr::SetSimple(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe) { TesterHideIndicators(true); ResetLastError(); this.atr_handle = iATR(this.symbol, timeframe, atr_period); TesterHideIndicators(false); SetSimple(atr_handle,atr_idx_,atr_multiplier_extra_be_,atr_multiplier_be_); }
Теперь определим функцию Set, которая будет наследовать от базового класса механизма безубыточности. Эта функция будет использоваться в классе-селекторе CBreakEven. Как я упоминал в предыдущей статье, мы не можем переопределить функцию SetSimple в базовом классе, потому что неизвестно, какие параметры она будет принимать. Именно поэтому функция Set позволяет работать с динамическими параметрами.
Для функции Set, поскольку у нас есть две версии функции SetSimple с разным количеством требуемых параметров, мы будем использовать размер наибольшего набора параметров для проверки корректного размера входных данных. Чтобы различать ситуации, когда параметры передаются с уже созданным дескриптором, и когда — без него, создадим определение (define) с именем HANDLE_INSTEAD_OF_PERIOD и значением по умолчанию 0.
#define HANDLE_INSTEAD_OF_PERIOD 0
Этот define будет находиться в последнем индексе массива params. Если его значение равно 0, мы предполагаем, что настройка выполняется с уже созданным дескриптором; в противном случае будет использована функция SetSimple без заранее определенного дескриптора.
Сказанное выше означает, что тело функции Set выглядит следующим образом:
//+------------------------------------------------------------------+ //| Función para establecer variables de CBreakEvenAtr con MqlParams | //+------------------------------------------------------------------+ void CBreakEvenAtr::Set(MqlParam ¶ms[]) { // Verificar tamaño correcto de params, para atr se requieren 5 parámetros if(params.Size() < 5) { printf("%s: Error crítico | El tamaño del array MqlParams %I32u es menor que 5", __FUNCTION__, params.Size()); ExpertRemove(); return; } // Si el último parámetro es HANDLE_INSTEAD_OF_PERIOD usamos SetSimple con handle if(params[4].integer_value == HANDLE_INSTEAD_OF_PERIOD) { SetSimple( (int)params[3].integer_value, // atr_handle (int)params[0].integer_value, // atr_idx params[2].double_value, // atr_multiplier_extra_be params[1].double_value // atr_multiplier_be ); } else { // Si no, usamos SetSimple con período y timeframe SetSimple( (int)params[4].integer_value, // atr_period (int)params[0].integer_value, // atr_idx params[2].double_value, // atr_multiplier_extra_be params[1].double_value, // atr_multiplier_be (ENUM_TIMEFRAMES)params[3].integer_value // timeframe ); } }
Функция для реализации безубытка
Для реализации режима безубытка по ATR мы переопределим функцию .Add. Она выполняется каждый раз при открытии новой позиции, то есть вызывается внутри функции OnTradeTransactionEvent базового класса. В теле метода .Add необходимо определить цену безубытка и цену, превышение которой активирует механизм безубытка.
Для расчета необходимых цен, таких как breakeven_price и price_to_beat (цена, которая активирует безубыток и изменяет Stop Loss позиции), мы будем использовать формулу, аналогичную той, что применяется в классе безубытка на основе фиксированных пунктов.
- Позиции на покупку:
break_even_price = open_price + (atr_buff[0] * atr_multiplier_extra_be)
- Позиции на продажу:
break_even_price = open_price - (atr_buff[0]* atr_multiplier_extra_be)
В этой формуле мы используем atr_multiplier_extra_be вместо extra_points_be для расчета цены безубытка. Для цены, которую нужно превзойти, — price_to_beat — формула будет такой же, только вместо atr_multiplier_extra_be мы используем переменную atr_multiplier_be.
После определения значений, которые необходимо присвоить, определяем функцию .Add. Сначала мы копируем данные ATR, начиная с this.atr_idx, копируя только один элемент. Проверяем, что значение, возвращаемое CopyBuffer, не меньше 1; если это не так, возвращаем false, отмечая, что добавить тикет в массив безубытка не удалось.
ResetLastError(); if(CopyBuffer(this.atr_handle, 0, this.atr_idx, 1, this.atr_buff) < 1) { printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError()); return false; }
Затем создаем структуру new_pos, содержащую важную информацию для функции BreakEven(). Присваиваем ее членам необходимые значения: цена безубытка, цена, которую необходимо превзойти (используя приведенные выше формулы), тикет и тип позиции (покупка или продажа).
position_be new_pos; new_pos.breakeven_price = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_extra_be) : open_price - (atr_buff[0] * atr_multiplier_extra_be); new_pos.type = position_type; new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_be) : open_price - (atr_buff[0] * atr_multiplier_be); new_pos.ticket = post_ticket; ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos);
Полный код функции .Add
//+------------------------------------------------------------------+ //| Function to add an element to the positions array | //+------------------------------------------------------------------+ bool CBreakEvenAtr::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type) { ResetLastError(); if(CopyBuffer(this.atr_handle, 0, this.atr_idx, 1, this.atr_buff) < 1) { printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError()); return false; } position_be new_pos; new_pos.breakeven_price = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_extra_be) : open_price - (atr_buff[0] * atr_multiplier_extra_be); new_pos.type = position_type; new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (atr_buff[0] * atr_multiplier_be) : open_price - (atr_buff[0] * atr_multiplier_be); new_pos.ticket = post_ticket; ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos); return true; }
Основы и проектирование класса CBreakEvenRR на основе RRR
Режим безубытка на основе RRR (risk-reward ratio), является еще одним динамическим типом безубытка, наряду с ATR. Вкратце, он заключается в перемещении Stop Loss на определенное расстояние от цены открытия, как только позиция достигает определенного уровня выше Stop Loss. Например, значение ratio, равное 1, указывает на то, что безубыток активируется, когда текущая цена становится больше или равной цене открытия, плюс величина Stop Loss в пунктах.
Для начала я внесу некоторые изменения. В предыдущей статье Stop Loss переносился на фиксированное количество пунктов от цены открытия. В этой новой части я добавлю возможность выполнять этот перенос на основе ATR, чтобы сделать безубыток на основе RRR еще более динамичным.
Теперь приступим к программированию.
Как я уже отметил, добавление ATR в качестве варианта для расчета цены безубытка требует создания перечисления, позволяющего выбирать способ расчета цены безубытка: либо по фиксированным пунктам, либо по значению ATR, умноженному на соответствующий множитель.
enum ENUM_TYPE_EXTRA_BE_BY_RRR { EXTRA_BE_RRR_BY_ATR, //By Atr EXTRA_BE_RRR_BY_FIXED_POINTS //By Fixed Points };
Класс будет публично наследоваться от базового класса CBreakEvenBase.
class CBreakEvenRR : public CBreakEvenBase { private:
Начнем с определения основных переменных. Нам понадобится переменная, в которой будет храниться коэффициент, при котором будет активироваться механизм безубыточности.
double coefficient_rr; //Coefficient of rr
Нам также потребуется сохранить тип расчета для цены безубытка.
ENUM_TYPE_EXTRA_BE_BY_RRR type;
Далее — переменная, в которой будет храниться либо множитель ATR, либо значение, уже умноженное на point_value, в зависимости от выбранного режима:
double extra_value_be; //Extra value that will be added to the opening price of the position to obtain the breakeven price //Note: if the type is atr this will contain the atr multiplier, if not it will contain the value already multiplied by the point value
Для интеграции ATR нам понадобятся три обязательные переменные: дескриптор, массив для хранения значений и целочисленная переменная для хранения индекса, с которого начнется копирование данных.
int handle_atr; int idx_atr; double atr_buff[];
Конструктор
Конструктор класса принимает те же три параметра, что и основной класс.
CBreakEvenRR(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_);
Внутри тела конструктора мы инициализируем внутренние переменные класса, а также вызываем конструктор базового класса.
//+------------------------------------------------------------------+ //| Contructor | //+------------------------------------------------------------------+ void CBreakEvenRR::CBreakEvenRR(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_) : CBreakEvenBase(symbol_, magic_, mode_), handle_atr(INVALID_HANDLE), idx_atr(0), coefficient_rr(1.0), extra_value_be(100.0), type(EXTRA_BE_RRR_BY_FIXED_POINTS) { ArraySetAsSeries(atr_buff, true); this.num_params = 6; }
Деструктор
В деструкторе освобождается массив, в котором хранятся данные ATR и дескриптор индикатора ATR.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CBreakEvenRR::~CBreakEvenRR(void) { ArrayFree(this.atr_buff); if(handle_atr != INVALID_HANDLE) IndicatorRelease(this.handle_atr); }
Функции для установки параметров
В функциях для установки параметров класса мы создадим еще две с логикой, аналогичной классу безубыточности на основе ATR. Одна из них будет принимать дескриптор, а другая — такие параметры, как таймфрейм и период ATR, для создания дескриптора непосредственно внутри функции.
Сначала определим функцию, принимающую дескриптор.
В начале проверяем, что отсутствуют некорректные значения переменных, и что тип безубытка, сохраненный в переменной type_extra, является допустимым.
//+------------------------------------------------------------------+ //| Function to set break even values by rr without MqlParams | //+------------------------------------------------------------------+ void CBreakEvenRR::SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_) { if(coefficient_rr <= 0.00) { printf("%s: Critical Error | The %+f coefficient of 'reward' is invalid", __FUNCTION__, coefficient_rr); ExpertRemove(); return; } if(atr_multiplier_or_extra_points <= 0.00) { printf("%s: Error | El atr multiplier o puntos extra %f es menor o igual a 0", __FUNCTION__, atr_multiplier_or_extra_points); ExpertRemove(); return; } if(type_extra != EXTRA_BE_RRR_BY_ATR && type_extra != EXTRA_BE_RRR_BY_FIXED_POINTS) { printf("%s: Erorr critico | El tipo de extra value %s, es invalido", __FUNCTION__, EnumToString(type_extra)); ExpertRemove(); return; }
Если какая-либо проверка не будет пройдена, мы удалим советник с текущего графика и выведем сообщение об ошибке.
Затем мы присваиваем внутренним переменным соответствующие значения:
this.type = type_extra; this.coefficient_rr = rr_a_put_the_break_even;
Если переменная type_extra имеет значение EXTRA_BE_RRR_BY_ATR, выполняем проверку индекса ATR.
if(type_extra == EXTRA_BE_RRR_BY_ATR) { if(idx_atr_ < 0) { printf("%s: Error | El index del atr %I32d es invalido", __FUNCTION__, idx_atr_); ExpertRemove(); return; } else this.idx_atr = idx_atr_;
Если проверка выполнена успешно, присваиваем внутренней переменной idx_atr значение параметра idx_atr_.
Далее проверяем, является ли допустимым дескриптор; если это не так, удаляем советник. Если проверка проходит успешно, присваиваем значение параметра atr_handle_ внутренней переменной handle_atr. Кроме того, переменной extra_value_be присваивается значение atr_multiplier_or_extra_points.
if(atr_handle_ == INVALID_HANDLE) { printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError()); ExpertRemove(); return; } else this.handle_atr = atr_handle_; this.extra_value_be = atr_multiplier_or_extra_points; } else this.extra_value_be = atr_multiplier_or_extra_points * this.point_value;
Теперь переходим ко второй функции, которая принимает параметры для создания дескриптора ATR.
void SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_);
В первую очередь мы проверяем, что значения параметров type_extra и atr_period_ являются допустимыми.
//+------------------------------------------------------------------+ //| Function to set break even values by rr without MqlParams | //+------------------------------------------------------------------+ void CBreakEvenRR::SetSimple(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_) { if(type_extra != EXTRA_BE_RRR_BY_ATR && type_extra != EXTRA_BE_RRR_BY_FIXED_POINTS) { printf("%s: Error critico | El tipo de extra value %s, es invalido", __FUNCTION__, EnumToString(type_extra)); ExpertRemove(); return; } if(atr_period_ < 0) { printf("%s: Error | El periodo del atr %I32d, es invalido", __FUNCTION__, atr_period_); ExpertRemove(); return; }
Если какое-либо из значений является недопустимым, удаляем советник с текущего графика.
Если type_extra имеет значение EXTRA_BE_RRR_BY_ATR, создаем дескриптор индикатора ATR с использованием параметров tf_atr и atr_period_.
if(type_extra == EXTRA_BE_RRR_BY_ATR) { TesterHideIndicators(true); ResetLastError(); this.handle_atr = iATR(this.symbol, tf_atr, atr_period_); TesterHideIndicators(false); }
В завершение вызываем первую функцию SetSimple.
SetSimple(rr_a_put_the_break_even, type_extra, atr_multiplier_or_extra_points, idx_atr_, this.handle_atr);
} Теперь переопределим функцию .Set(), которая используется для установки параметров класса и наследуется от базового класса безубыточности.
void Set(MqlParam ¶ms[]) override;
Как и в функции Set, сначала проверяем чтобы размер массива params не был меньше количества параметров, требуемых данным классом.
//+------------------------------------------------------------------+ //| Function to set break even values by rr with MqlParams | //+------------------------------------------------------------------+ void CBreakEvenRR::Set(MqlParam ¶ms[]) { if((int)params.Size() < num_params) { printf("%s: Error | The size of the MqlParams array %I32u to set the be by rr is less than 2", __FUNCTION__, params.Size()); ExpertRemove(); return; }
Если проверка не пройдена, удаляем советник.
Для использования массива params и установки внутренних значений класса применяется define HANDLE_INSTEAD_OF_PERIOD. Этот define указывает, содержат ли параметры в массиве params уже готовый дескриптор. Если это так, используется первая функция SetSimple, если нет —вызывается вторая функция SetSimple, которая создает дескриптор.
if(params[5].integer_value == HANDLE_INSTEAD_OF_PERIOD) { //-> (0)double rr_a_put_the_break_even,(1) ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, (2)double atr_multiplier_or_extra_points, (3)int idx_atr_, (4)int atr_handle_ SetSimple(params[0].double_value, (ENUM_TYPE_EXTRA_BE_BY_RRR)params[1].integer_value, params[2].double_value, (int)params[3].integer_value, (int)params[4].integer_value); } else { //-> (0)double rr_a_put_the_break_even,(1) ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, (2)double atr_multiplier_or_extra_points, (3)int idx_atr_, (4)ENUM_TIMEFRAMES tf_atr, (5)int atr_period_ SetSimple(params[0].double_value, (ENUM_TYPE_EXTRA_BE_BY_RRR)params[1].integer_value, params[2].double_value, (int)params[3].integer_value, (ENUM_TIMEFRAMES)params[4].integer_value, (int)params[5].integer_value); } }
Функция .Add
Внутри функции Add мы приступаем к установке значений, которые требуются классу CBreakEvenBase для корректного выполнения функции CBreakEven, отвечающей за применение механизма безубыточности к открытым позициям.
В первую очередь проверяем, что у позиции установлен Stop Loss:
//+------------------------------------------------------------------+ //| Function to add an element to the positions array | //+------------------------------------------------------------------+ bool CBreakEvenRR::Add(ulong post_ticket, double open_price, double sl_price, ENUM_POSITION_TYPE position_type) { if(sl_price <= 0.00) { printf("%s: Error | Position %I64u with stop loss %+f has sl less than 0", __FUNCTION__, post_ticket, sl_price); return false; }
Если Stop Loss отсутствует, возвращаем false, и позиция не будет добавлена в массив позиций PositionsBe.
Далее создаем переменную типа double с именем val, которая будет хранить расстояние от цены открытия, на котором будет размещен уровень Stop Loss. Если переменная type имеет значение EXTRA_BE_RRR_BY_ATR, мы копируем данные ATR, начиная с внутренней переменной idx_atr, копируя только один элемент. Если копируется менее одного элемента, возвращаем false и выводим сообщение об ошибке. В противном случае умножаем val на значение в индексе 0 массива atr_buff.
double val = this.extra_value_be; if(type == EXTRA_BE_RRR_BY_ATR) { ResetLastError(); if(CopyBuffer(this.handle_atr, 0, this.idx_atr, 1, this.atr_buff) < 1) { printf("%s: Error | When copying the data of the indicator atr, last error %I32d", __FUNCTION__, GetLastError()); return false; } else val *= this.atr_buff[0]; }
Далее проверяем, чтобы расстояние, на котором будет установлен Stop Loss, было допустимым, то есть меньшим, чем расстояние, при котором будет активирован механизм безубыточности, чтобы избежать ошибок типа invalid stops.
double diff = MathAbs(open_price - sl_price); if((diff * coefficient_rr) <= val) { printf("%s: Error | The distance from the opening price %f where the stoploss is located is greater than or equal to the price to trigger the breakeven", __FUNCTION__, this.extra_value_be); return false; }
В завершение, если все проверки успешно пройдены, мы рассчитываем новое значение цены Stop Loss — breakeven_price, а также цену, которую необходимо превысить для активации режима безубытка — price_to_beat, используя следующие формулы:
Для нового Stop Loss (breakeven_price):
- Позиции на покупку:
break_even_price = open_price + val
- Позиции на продажу:
break_even_price = open_price - val
Для цены активации безубытка price_to_beat:
- Позиции на покупку:
price_to_beat = open_price + (coefficient_rr * diff)
- Позиции на продажу:
price_to_beat = open_price - (coefficient_rr * diff)
Создаем структуру position_be и присваиваем ей необходимые значения: тип позиции, тикет и два ценовых уровня (breakeven_price и price_to_beat).
После этого добавляем эту структуру в массив PositionsBe.
position_be new_pos; new_pos.breakeven_price = position_type == POSITION_TYPE_BUY ? open_price + val : open_price - val; new_pos.type = position_type; new_pos.price_to_beat = position_type == POSITION_TYPE_BUY ? open_price + (coefficient_rr * diff) : open_price - (coefficient_rr * diff); new_pos.ticket = post_ticket; ExtraFunctions::AddArrayNoVerification(this.PostionsBe, new_pos); return true; }
Класс CBreakEven: выбор и динамическая настройка режима безубытка
Для эффективного выбора режима безубытка, который будет использоваться, мы создадим класс-селектор. Он будет отвечать за выполнение логики безубыточности и за преобразование внутреннего указателя в производный тип в зависимости от выбранного пользователем типа безубытка.
Начнем с определения структуры BreakEvenParams, которая будет содержать массив типа MqlParam, где будут храниться параметры конкретного типа безубытка.
struct BreakEvenParams { MqlParam params[]; };
Теперь объявим сам класс и его приватную часть.
//--- class CBreakEven { private: ulong magic; string symbol; ENUM_BREAKEVEN_MODE breakeven_mode; BreakEvenParams parameters[]; CBreakEvenBase* CreateBreakEven(ENUM_BREAKEVEN_TYPE type);
Приватная часть содержит магический номер, символ и режим безубытка. Также в ней присутствует массив типа BreakEvenParams для хранения параметров трех типов безубытка, которые будут рассматриваться. Также объявляется функция, возвращающая указатель типа CBreakEvenBase; она будет вызвана в одной из последующих функций для установки публичного указателя.
В завершение создается переменная типа CBreakEvenBase*, которая будет настраиваться и приводиться к нужному типу в функции SetInternalPointer, в зависимости от выбранного пользователем режима безубытка.
CBreakEvenBase* obj;
Конструктор
Конструктор будет иметь те же параметры, что и конструктор базового класса безубытка.
CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode);
Внутри конструктора мы инициализируем значения внутренних переменных и устанавливаем размер массива параметров равным 3, что соответствует общему количеству параметров.
//+------------------------------------------------------------------+ //| Contructor | //+------------------------------------------------------------------+ CBreakEven::CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode) { this.magic = magic_; this.symbol = symbol_; this.breakeven_mode = mode; ArrayResize(parameters, 3); }
Деструктор
Внутри деструктора мы сначала освобождаем массив parameters.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CBreakEven::~CBreakEven() { ArrayFree(this.parameters);
Перед удалением указателя безубытка obj, проверяем, является ли он динамическим, и если да, то удаляем его.
if(CheckPointer(this.obj) == POINTER_DYNAMIC) { delete obj; obj = NULL; } }
Функции для установки параметров
Для задания значений внутри массива параметров мы создадим пять функций, которые позволят нам настроить параметры трех режимов безубытка: две — для ATR, две — для RR и одну — для безубытка по фиксированным пунктам.
Функции для установки параметров безубытка по RR
Начнем с функций для RR. Как уже упоминалось, их будет две. Одна требует передачи дескриптора ATR:
void SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_);
И другая, для которой требуются общие параметры и параметры для создания дескриптора ATR:
void SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_);
Начнем с определения первой функции, которая требует передачи дескриптора ATR. В этой функции, как и в остальных, мы сначала устанавливаем размер массива params равным 6.
ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
Затем присваиваем каждому индексу массива params соответствующее значение.
parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even; parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra; parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points; parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_; parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = atr_handle_;
Последний индекс, 5, будет иметь значение HANDLE_INSTEAD_OF_PERIOD, чтобы функция установки параметров класса CBreakEvenRR могла определить, какую функцию следует вызывать; в данном случае ту, которая требует передачи дескриптора.
parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD; Для второй функции шаги аналогичны, однако теперь индекс 4, который ранее содержал значение дескриптора ATR, будет содержать таймфрейм ATR, а индекс 5 — период ATR.
//+------------------------------------------------------------------+ //| Set rr without handle | //+------------------------------------------------------------------+ void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_) { ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6); parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even; parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra; parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points; parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_; parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = (int)tf_atr; parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = atr_period_; }
Полный код
//+------------------------------------------------------------------+ //| Set rr without handle | //+------------------------------------------------------------------+ void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, ENUM_TIMEFRAMES tf_atr, int atr_period_) { ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6); parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even; parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra; parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points; parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_; parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = (int)tf_atr; parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = atr_period_; } //+------------------------------------------------------------------+ //| Set rr with handle | //+------------------------------------------------------------------+ void CBreakEven::SetBeByRR(double rr_a_put_the_break_even, ENUM_TYPE_EXTRA_BE_BY_RRR type_extra, double atr_multiplier_or_extra_points, int idx_atr_, int atr_handle_) { ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6); parameters[BREAKEVEN_TYPE_RR].params[0].double_value = rr_a_put_the_break_even; parameters[BREAKEVEN_TYPE_RR].params[1].integer_value = (int)type_extra; parameters[BREAKEVEN_TYPE_RR].params[2].double_value = atr_multiplier_or_extra_points; parameters[BREAKEVEN_TYPE_RR].params[3].integer_value = idx_atr_; parameters[BREAKEVEN_TYPE_RR].params[4].integer_value = atr_handle_; parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD; }
Функции для установки параметров безубытка по ATR
Для задания значений массива params, мы также будем использовать две функции, как в случае безубытка по RR, но с меньшим количеством параметров.
Обе функции для установки параметров будут иметь ровно пять параметров. Первую из них, для которой требуется дескриптор, мы определим следующим образом:
void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle)
При описании этой функции первым шагом устанавливается размер массива параметров внутри массива params:
ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);
Затем присваиваются значения всем пяти элементам массива params:
parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx; parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be; parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be; parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = atr_handle;
Напомню, что последний элемент, индекс 4, должен иметь значение HANDLE_INSTEAD_OF_PERIOD, чтобы можно было выбрать функцию-член класса CBreakEvenAtr, которая требует передачи дескриптора:
parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD;
Для второй функции меняются только значения индексов 3 и 4.
Индекс 3 будет содержать значение таймфрейма ATR:
parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe);
А индекс 4 — значение периода ATR:
parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period;
Полный код
//+------------------------------------------------------------------+ //| Set atr (without handle) | //+------------------------------------------------------------------+ void CBreakEven::SetBeByAtr(int atr_period, int atr_idx_, double atr_multiplier_extra_be_, double atr_multiplier_be_, ENUM_TIMEFRAMES timeframe) { ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5); parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx_; parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be_; parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be_; parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe); parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period; } //+------------------------------------------------------------------+ //| Set atr (with handle) | //+------------------------------------------------------------------+ void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle) { ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5); parameters[BREAKEVEN_TYPE_ATR].params[0].integer_value = atr_idx; parameters[BREAKEVEN_TYPE_ATR].params[1].double_value = atr_multiplier_be; parameters[BREAKEVEN_TYPE_ATR].params[2].double_value = atr_multiplier_extra_be; parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = atr_handle; parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD; }
Функция для установки параметров безубытка по фиксированным пунктам
Для завершения настройки остается безубыток по пунктам, для которого требуется всего два параметра. Внутри класса CBreakEven мы объявим его следующим образом:
void SetBeByFixedPoints(int points_be_, int extra_points_be_);
Для его определения сначала установим размер соответствующего массива params равным 2:
ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2);
Затем присваиваем значению индекса 0 количество пунктов для активации безубытка и значению индекса 1 — количество дополнительных пунктов.
parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_; parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_;
Полный код
//+------------------------------------------------------------------+ //| Set Fixed Point be | //+------------------------------------------------------------------+ void CBreakEven::SetBeByFixedPoints(int points_be_, int extra_points_be_) { ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2); parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_; parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_; }
Функция для получения указателя на основе типа безубытка
Для получения необходимого режима безубытка в зависимости от типа, заданного пользователем, мы создадим функцию, которая будет возвращать указатель на CBreakEvenBase.
CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type)
Начнем с создания оператора switch, где каждый вариант соответствует одному типу безубытка. Если переменная type не соответствует ни одному из них, по умолчанию будет возвращено значение NULL. Если совпадение будет найдено, функция вернет указатель на соответствующий дочерний класс.
switch(type) { case BREAKEVEN_TYPE_ATR: return new CBreakEvenAtr(this.symbol, this.magic, this.breakeven_mode); case BREAKEVEN_TYPE_RR: return new CBreakEvenRR(this.symbol, this.magic, this.breakeven_mode); case BREAKEVEN_TYPE_FIXED_POINTS: return new CBreakEvenSimple(this.symbol, this.magic, this.breakeven_mode); default: return NULL; }
Полный код
//+------------------------------------------------------------------+ //| Dynamically create the correct BreakEven | //+------------------------------------------------------------------+ CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type) { switch(type) { case BREAKEVEN_TYPE_ATR: return new CBreakEvenAtr(this.symbol, this.magic, this.breakeven_mode); case BREAKEVEN_TYPE_RR: return new CBreakEvenRR(this.symbol, this.magic, this.breakeven_mode); case BREAKEVEN_TYPE_FIXED_POINTS: return new CBreakEvenSimple(this.symbol, this.magic, this.breakeven_mode); default: return NULL; } return NULL; }
Функция для установки внутреннего значения указателя obj
В завершение, для присвоения значения внутреннему указателю, мы создадим функцию с именем SetInternalPointer, которая будет принимать в качестве параметра тип безубытка:
void SetInternalPointer(ENUM_BREAKEVEN_TYPE type) Определение функции начинаем с проверки состояния указателя obj.
if(CheckPointer(this.obj) == POINTER_DYNAMIC) { delete this.obj; this.obj = NULL; }
Если указатель является динамическим, мы удаляем его.
Затем указатель obj инициализируется функцией CreateBreakEven, которая возвращает указатель в зависимости от выбранного типа безубытка. После присвоения мы проверяем, не равен ли он NULL. В этом случае выводим сообщение об ошибке и удаляем советник с текущего графика.
this.obj = CreateBreakEven(type); if(this.obj == NULL) { printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type); ExpertRemove(); return; }
Проверяем, если размер массива params достаточен для хранения необходимых параметров. Если эта проверка не пройдена, мы удаляем объект, устанавливаем указатель в NULL и удаляем советник с графика.
if((int)parameters[type].params.Size() < obj.GetNumParams()) { printf("%s: Error | The parameter array for %s is too small (%I32u elements)", __FUNCTION__, EnumToString(type), parameters[type].params.Size()); delete obj; obj = NULL; ExpertRemove(); return; }
В завершение устанавливаем необходимые значения, используя функцию Set базового класса:
obj.Set(parameters[type].params);
Полный код
//+------------------------------------------------------------------+ //| Set Pointer | //+------------------------------------------------------------------+ void CBreakEven::SetInternalPointer(ENUM_BREAKEVEN_TYPE type) { if(CheckPointer(this.obj) == POINTER_DYNAMIC) { delete this.obj; this.obj = NULL; } this.obj = CreateBreakEven(type); if(this.obj == NULL) { printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type); ExpertRemove(); return; } if((int)parameters[type].params.Size() < obj.GetNumParams()) { printf("%s: Error | The parameter array for %s is too small (%I32u elements)", __FUNCTION__, EnumToString(type), parameters[type].params.Size()); delete obj; obj = NULL; ExpertRemove(); return; } obj.Set(parameters[type].params); } //+------------------------------------------------------------------+
Тестирование советника Order Blocks с различными типами безубытка
В этом заключительном разделе мы сосредоточимся на тестировании трех режимов безубытка в тестере стратегий.
Начнем с настройки параметров. Мы будем использовать Stop Loss и Take Profit на основе ATR. Для Stop Loss будет задан множитель ATR равный 3,0, а Take Profit — 6,0, то есть с соотношением 1:2. Торговый интервал будет установлен с 03:00 до 14:00 по GMT-3.
Настройки

Рисунок 1: Настройки для бэктеста советника Order Blocks
Как видно на изображении, робот будет протестирован на золоте (XAUUSD), и мы выберем таймфрейм M5 (5 минут). Модель будет основана на реальных тиках, с кредитным плечом 1:30 и начальным депозитом в 10 000 USD.
Начальный бэктест без активации безубытка
Для более наглядного сравнения, первое тестирование будет проведено без активации режима безубытка.

Рисунок 2: График бэктеста советника Order Blocks без активации безубытка
Перед началом бэктестов важно отметить, что полученные результаты справедливы для конкретных параметров, поэтому представленные выводы нельзя обобщать на любую стратегию или произвольный набор параметров. Однако, можно выполнить приближенную оценку для схожих значений.
Например, в представленном ниже бэктесте мы проверим безубыток по фиксированным пунктам. В качестве параметров будут установлены 100 дополнительных пунктов для цены безубытка и 200 пунктов для его активации. Таким образом, выводы, сделанные по результатам этого бэктеста будут относиться к значениям, близким к 200 и 100 пунктам, для пунктов активации безубытка и дополнительных пунктов соответственно.
Бэктест для безубытка по пунктам
Для бэктеста по фиксированным пунктам мы выберем 200 пунктов для активации безубытка и 100 дополнительных пунктов. Ниже представлены полученные результаты.

Рисунок 3: График работы советника Order Blocks с безубытком по фиксированным пунктам
Как видно на изображении, наблюдалось множество серий позиций с небольшой прибылью. Они, как и ожидалось, были затронуты механизмом безубытка. Кроме того, в этом бэктесте с безубытком по фиксированным пунктам позиции, которые имели положительный плавающий результат, приблизительно в период с 16 по 29 апреля 2025 года, закрывались без убытков. В то время как в первом бэктесте в этот период фиксировались потери.
Это может указывать на то, что безубыток по пунктам является более консервативным подходом, но он также способен ограничивать будущую прибыль. В результате, в этом бэктесте итоговый баланс составил всего 12 000 USD, тогда как в первом бэктесте итоговый баланс составлял приблизительно 15 800 USD. Это говорит о том, что безубыток по фиксированным пунктам хотя и снижает потери, одновременно существенно ограничивает прибыль.
Бэктест с безубытком по ATR

Рисунок 4: График работы советника Order Blocks с безубытком по ATR
Во втором бэктесте с безубытком по ATR на графике наблюдается улучшение. В первую очередь мы проанализируем влияние этого типа безубытка на положительные и отрицательные серии сделок.
В положительных сериях этот безубыток действует не столь агрессивно, как безубыток по фиксированным пунктам. В отрицательных сериях он умеренно ограничивает убытки. Это хорошо заметно на первой крупной убыточной серии, которая продолжалась с начала 2024 года до марта. Кривая баланса данного бэктеста во многом схожа с кривой первого бэктеста.
Таким образом, можно сделать вывод, что этот режим безубытка является более умеренным, возможно, не таким агрессивным, как безубыток по фиксированным пунктам, поскольку в зависимости от волатильности, цена его активации может быть как выше, так и ниже — соответственно значению ATR.
Бэктест c безубытком по RR

Рисунок 5: График работы советника Order Blocks с безубытком по RR, при соотношении Stop Loss – Take Profit 1:2
По результатам этого бэктеста можно отметить, что в отрицательных сериях, особенно в январе и феврале 2024 года, разница между бэктестом без безубытка и вариантом с безубытком по RR невелика. Это говорит о том, что данные убытки были реальными, то есть отсутствовал положительный плавающий результат: после открытия позиции цена сразу достигала Stop Loss, поэтому безубыток по RR не имел возможности активироваться.
В положительных сериях рост прибыли ограничивается умеренно. Например, в июле 2024 года, когда в первом бэктесте почти все позиции закрывались по Take Profit, при использовании безубытка по RR эта серия фактически застопорилась. Это может быть связано с тем, что советник торговал в диапазонах консолидации, где цена не формировала выраженного тренда, что позволяло механизму безубытка активироваться и закрывать сделку до того, как тренд приобретал направление.
Можно сделать вывод, что безубыток по RR является более консервативным, поскольку для его активации цена должна пройти большее расстояние. Поэтому он не является оптимальным вариантом для агрессивных стратегий, но может быть полезен для стратегий типа swing, ориентированных на высокое соотношение риск–прибыль, например 1:3 или 1:5. Чтобы проверить это, далее мы проведем бэктест с соотношением риск–прибыль 1:3.

Рисунок 6: График работы советника Order Blocks с безубытком по RR, при соотношении Stop Loss – Take Profit 1:3.
Новый бэктест показал лучшие результаты, чем предыдущий, с соотношением 1:2. Основные изменения заключаются в том, что минимальный баланс составил около 9500 USD, что практически совпадает с результатом бэктеста с безубытком по ATR. Кроме того, во время первой отрицательной серии, в период с января по февраль, убытки были не такими значительными, как в исходном бэктесте.
Однако, в апреле наблюдается самая продолжительная отрицательная серия среди всех трех типов безубытка — почти пять месяцев убытков. Это совпадает с периодом бокового движения золота продолжительностью около пяти месяцев. По сравнению с первым бэктестом безубытка по RR, это может указывать на то, что позиции закрывались не сразу, а, вероятно, испытывали колебания между отрицательным и положительным плавающим результатом, поэтому размер Stop Loss и Take Profit должен был быть значительным.
Проблема в том, что в этом новом бэктесте размер Stop Loss был уменьшен до одной трети от первоначального, поэтому вероятность закрытия позиций с убытком возросла. Это может указывать на то, что в период бокового движения время удержания сделок было больше, и что перед закрытием наблюдались колебания между положительными и отрицательными значениями.
Заключение
В этой статье мы завершили реализацию безубытка по ATR и RR в MQL5. Затем мы создали класс, который позволяет управлять различными типами безубытка в рамках одного класса, что упрощает переключение между ними без необходимости повторного ввода параметров для каждого нового режима.
В завершение мы протестировали и проанализировали тип безубытка. Мы увидели, что безубыток по фиксированным пунктам при заданных параметрах является более агрессивным. Напротив, безубыток по RR является более пассивным, но сильнее ограничивает прибыль. Безубыток по ATR занимает промежуточное положение между этими двумя вариантами.
Подводя итог, следует отметить, что невозможно с уверенностью сказать, какой тип безубытка является наилучшим для каждой конкретной стратегии, поскольку это зависит от ряда факторов, которые необходимо принимать во внимание. Это скорее задача для самостоятельного анализа. Тем не менее, в качестве рекомендации можно предложить тестировать различные типы безубытка и выбирать тот, который лучше всего адаптируется к используемой стратегии.
Так, по результатам трех бэктестов можно предположить, что безубыток по RR лучше подходит для стратегий с заранее заданным соотношением риск–прибыль, большим или равным 1:3. Это было подтверждено в пятом бэктесте, где чистая прибыль оказалась почти в три раза выше, чем в четвертом.
Файлы, использованные/обновленные в этой статье:
| Имя файла | Тип | Описание |
|---|---|---|
| Risk_Management.mqh | .mqh (заголовочный файл) | Содержит класс управления рисками, разработанный в последней статье серии по управлению рисками. |
| Order_Block_Indicador_New_Part_2.mq5 | .mq5 (индикатор) | Содержит код индикатора Order Block. |
| Order Block EA MT5.mq5 | .mq5 (советник) | Код советника Order Block с интегрированным механизмом безубытка. |
| OB_SET_WITHOUT_BREAKEVEN.set | .set (файл конфигурации) | Настройки первого бэктеста, без механизма безубыточности. |
| OB_SET_BREAKEVEN_POINTS.set | .set (файл конфигурации) | Настройки второго бэктеста с безубытком по фиксированным пунктам. |
| OB_SET_BREAKEVEN_ATR.set | .set (файл конфигурации) | Настройки третьего бэктеста с безубытком по ATR. |
| OB_SET_BREAKEVEN_RR_1_2.set | .set (файл конфигурации) | Настройки четвертого бэктеста с безубытком по RR при соотношении 1:2. |
| OB_SET_BREAKEVEN_RR_1_3.set | .set (файл конфигурации) | Настройки пятого бэктеста с безубытком по RR при соотношении 1:3. |
| PositionManagement.mqh | .mqh (заголовочный файл) | Файл mqh, содержащий код механизма безубыточности. |
Перевод с испанского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/es/articles/18111
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От новичка до эксперта: Алгоритмическая дисциплина трейдера — советник Risk Enforcer вместо эмоций
Как торговать Fair Value Gaps: правила формирования, сценарии отработки и автоторговля с помощью прерывателей и сдвигов структуры рынка
Знакомство с языком MQL5 (Часть 27): Освоение API и функции WebRequest в языке MQL5
Математика волатильности: Почему индикатор GRI достоин возвращения в ваш торговый терминал
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования