Implementación de Breakeven en MQL5 (Parte 2): Breakeven basado en ATR y RRR
- Introducción
- Desarrollo de la clase CBreakEvenAtr basada en el indicador ATR
- Fundamentos y diseño de la clase CBreakEvenRR basada en RRR
- Clase CBreakEven: selección y configuración dinámica de un único tipo de breakeven
- Backtesting del bot de order blocks con distintos tipos de breakeven
- Conclusión
Introducción
Bienvenidos a esta nueva entrega sobre el breakeven. En este artículo continuaremos programando los distintos tipos de breakeven que quedaron pendientes en la primera parte. Además, crearemos una clase adicional que facilitará la elección del breakeven por parte del usuario. Primero abordaremos el breakeven basado en atr, luego el relacionado con el RRR, y finalmente la clase para usar el breakeven. Por último, realizaremos una comparación para evaluar cuál es el tipo de breakeven más adecuado para el bot de order blocks construido inicialmente en el último artículo de gestión de riesgo.
Desarrollo de la clase CBreakEvenAtr basada en el indicador atr
El breakeven por atr, como mencioné en el artículo anterior, consiste en no usar puntos fijos para la distancia donde se activará el breakeven ni para la diferencia entre el precio del breakeven y la apertura de la posición. En cambio, se usan multiplicadores que se aplican al valor del atr. Esto permite configurar con mayor flexibilidad el margen para la distancia del breakeven y el punto donde se activará.
Un atr multiplier alto significa que normalmente se da más margen, por lo que el breakeven se activará menos veces.
En cambio, un valor más bajo puede hacer que el breakeven se active más veces de lo habitual, lo que podría perjudicar la obtención de profit si dejamos correr la operación.
Para implementar este enfoque en MQL5, crearemos la clase CBreakEvenAtr, que heredará de "CBreakEvenBase", la clase base para cualquier tipo de breakeven.
//--- class CBreakEvenAtr class CBreakEvenAtr : public CBreakEvenBase
Para usar el atr necesitaremos un handle, que nos permitirá obtener la información del indicador, es decir, copiar su data. Además, usaremos un array doble llamado "atr_buff" que almacenará los datos copiados del indicador.
También se tendrá un entero "atr_idx" que indicará el índice desde donde se copiarán los datos; por ejemplo, un valor 0 indica que se copiarán los datos desde el valor actual del atr. Siempre se copiará un solo dato.
Luego necesitaremos dos multiplicadores: uno para la distancia donde se ubicará el breakeven y otro para la distancia donde se activará.
Variables privadas.
private: int atr_handle; double atr_buff[]; int atr_idx; double atr_multiplier_be; double atr_multiplier_extra_be;
Constructor y Destructor
En el constructor inicializaremos las variables privadas con valores por defecto. Además, pondremos en serie el array "atr_buff", que almacenará el valor del atr. También, definiremos que la clase requiere cinco parámetros, por lo que la variable "num_params" tendrá ese valor.
El constructor tendrá tres parámetros que se pasan a la clase base "CBreakEvenBase" para su inicialización.
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; }
En el destructor se liberará el handle del indicador atr y se liberará el array que almacena los valores del atr:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CBreakEvenAtr::~CBreakEvenAtr(void) { ArrayFree(atr_buff); if(this.atr_handle != INVALID_HANDLE) IndicatorRelease(this.atr_handle); }
Funciones para establecer parámetros
Continuando con la definición y declaración de la clase para el breakeven por atr, ahora toca definir métodos para establecer las variables internas. Para empezar definiremos las funciones "SetSimple". Estas sirven para establecer los parámetros requeridos por la clase sin usar "MqlParams", ideales si ya se sabe que el tipo de breakeven siempre será por atr. Dentro de estas funciones hay dos versiones.
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);
En la primera función "SetSimple", primeramente verificaremos que los parámetros como los multiplicadores sean válidos.
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(); }
Comprobamos que el multiplicador para activar el breakeven sea menor que el multiplicador donde se ubicará el breakeven, para evitar errores como stops inválidos.
Luego verificamos que el índice del atr sea siempre mayor o igual a cero.
if(atr_idx_ < 0) { printf("%s: Critical error | Atr index is less than 0", __FUNCTION__); ExpertRemove(); }
Finalmente, comprobamos que el handle del indicador atr no sea inválido; si lo es, se removerá el asesor experto del gráfico.
if(atr_handle_ == INVALID_HANDLE) { printf("%s: Critical Error | The handle of the indicator atr is invalid, last error: %I32d", __FUNCTION__, GetLastError()); ExpertRemove(); }
Después de estas verificaciones, asignamos los parámetros a las variables internas de la clase.
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_;
Código completo.
//+------------------------------------------------------------------------------------+ //| 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_; }
La segunda función "SetSimple" requiere un parámetro extra. En lugar de recibir el handle, pide el período del atr, ya que configuraremos el handle dentro de esta función. Después se llama a la primera función "SetSimple", pasándole el handle creado.
//+------------------------------------------------------------------------------------+ //| 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_); }
Ahora definiremos la función "Set", que heredará de la clase base del breakeven. Esta función será útil en la clase seleccionadora "CBreakEven". Como mencioné en el artículo anterior, no podemos sobrescribir la función "SetSimple" en la clase base porque no sabemos qué parámetros tendrá. Por eso, "Set" permite usar parámetros dinámicos.
Para esta función "Set", como tenemos dos versiones de "SetSimple" que requieren distinto número de parámetros, usaremos el tamaño del mayor para verificar el tamaño de parámetros. Para distinguir si se usan valores con handle ya definido o sin handle, crearemos un define llamado "HANDLE_INSTEAD_OF_PERIOD", con valor 0 por defecto.
#define HANDLE_INSTEAD_OF_PERIOD 0
Este define estará en el último índice del array "params". Si el valor es 0, asumiremos que se configura con un handle ya establecido; de lo contrario, usaremos la función "SetSimple" sin handle definido.
Dicho esto, el cuerpo de la función "Set" queda así:
//+------------------------------------------------------------------+ //| 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 ); } }
Funcion para hacer el breakeven
Para implementar el breakeven por atr, sobrescribiremos la función ".Add". Esta se ejecuta cada vez que se abre una nueva posición, es decir, se llama dentro de la función "OnTradeTransactionEvent" de la clase base. En el cuerpo de .Add debemos definir el precio del breakeven y el precio que, al ser superado, activará el breakeven.
Para calcular los precios necesarios, como "breakeven_price" y "price_to_beat" (precio que activará el breakeven y modificará el stoploss de la posición), usaremos una fórmula similar a la de la clase de breakeven por puntos fijos.
- Posiciones de compra:
break_even_price = open_price + (atr_buff[0] * atr_multiplier_extra_be)
- Posiciones de venta:
break_even_price = open_price - (atr_buff[0]* atr_multiplier_extra_be)
En esta fórmula usamos "atr_multiplier_extra_be" en lugar de "extra_points_be" para calcular el precio del breakeven. Para el precio a superar "price_to_beat", la fórmula es igual, solo que en vez de usar "atr_multiplier_extra_be" usamos la variable atr_multiplier_be.
Una vez definidos los valores a asignar, definimos la función .Add. Primero copiamos los datos del atr desde "this.atr_idx", copiando solo un elemento. Verificamos que el valor retornado por CopyBuffer sea al menos 1; si no, retornamos falso, lo que indica que no se pudo agregar el ticket al array del breakeven.
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; }
Luego creamos una estructura "new_pos" que contiene información importante para la función "BreakEven()". Asignamos a sus miembros los valores necesarios: el precio del breakeven, el precio a batir con las fórmulas anteriores, el ticket y el tipo de posición (compra o venta).
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);
Código completo de la función .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; }
Fundamentos y diseño de la clase CBreakEvenRR basada en RRR
El breakeven por rrr (risk-reward-ratio) es otro tipo de breakeven dinámico, junto con el atr. En resumen, consiste en mover el stoploss a cierta distancia desde el precio de apertura una vez que la operación alcance un ratio determinado sobre el stoploss. Por ejemplo, un ratio de 1 indica que se activa el breakeven cuando el precio actual es mayor o igual al precio de apertura más los puntos del stoploss.
Para empezar la clase haré algunos cambios. Normalmente, en el artículo anterior movía el stoploss a puntos fijos desde el precio de apertura. En esta nueva entrega agregaré la posibilidad de hacerlo con atr, para hacer el breakeven por rrr más dinámico de lo que ya era.
Ahora, comenzamos a programar.
Como mencioné antes, al agregar el atr como opción para calcular el precio del breakeven, tendremos que crear una enumeración para elegir el cálculo del precio del breakeven, ya sea por puntos fijos o por el valor del atr multiplicado por un multiplier.
enum ENUM_TYPE_EXTRA_BE_BY_RRR { EXTRA_BE_RRR_BY_ATR, //By Atr EXTRA_BE_RRR_BY_FIXED_POINTS //By Fixed Points };
La clase heredará públicamente de la clase base CBreakEvenBase.
class CBreakEvenRR : public CBreakEvenBase { private:
Empezaremos definiendo las variables principales. Necesitaremos una variable que almacene el ratio al que se activará el breakeven.
double coefficient_rr; //Coefficient of rr
También necesitaremos guardar el tipo de cálculo para el precio del breakeven.
ENUM_TYPE_EXTRA_BE_BY_RRR type;
Luego, una variable que almacene el multiplicador del atr o el valor ya multiplicado por point_value, según el tipo:
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
Para integrar el atr necesitaremos tres variables obligatorias: un handle, un array para almacenar los valores y un entero que almacene el índice desde donde empezar a copiar datos.
int handle_atr; int idx_atr; double atr_buff[];
Constructor
El constructor de la clase requiere los mismos tres parámetros que la principal.
CBreakEvenRR(string symbol_, ulong magic_, ENUM_BREAKEVEN_MODE mode_);
Dentro del cuerpo del constructor inicializaremos las variables internas de la clase, además de llamar al constructor de la clase base.
//+------------------------------------------------------------------+ //| 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; }
Destructor
En el destructor liberamos el array que almacena la data del atr y el handle del indicador atr.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CBreakEvenRR::~CBreakEvenRR(void) { ArrayFree(this.atr_buff); if(handle_atr != INVALID_HANDLE) IndicatorRelease(this.handle_atr); }
Funciones para establecer parámetros
En las funciones para establecer los parámetros de la clase, crearemos dos con funcionamiento similar a la clase de breakeven por atr. Una requerirá el handle y otra pedirá parámetros como timeframe y período del atr para crear el handle dentro de la función.
Primero definimos la función que requiere el handle.
Para comenzar, verificamos que no haya variables inválidas y que el tipo de breakeven almacenado en la variable "type_extra" sea válido.
//+------------------------------------------------------------------+ //| 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; }
Si alguna verificación falla, removeremos el asesor experto del gráfico actual e imprimiremos un mensaje de error.
Luego asignamos a las variables internas los valores correspondientes:
this.type = type_extra; this.coefficient_rr = rr_a_put_the_break_even;
Si la variable "type_extra" es "EXTRA_BE_RRR_BY_ATR", hacemos una comprobación del índice del 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_;
Si la comprobación es correcta, asignamos a la variable interna "idx_atr" el valor de idx_atr_.
Seguidamente, verificamos que el handle sea válido; si no, removemos el asesor experto. Si es correcto, asignamos el valor del parámetro "atr_handle_" a la variable interna
"handle_atr". Además, asignamos "extra_value_be" como "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;
Ahora continuamos con la segunda función, que requiere parámetros para crear el handle del 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_);
Primero verificamos que el valor de "type_extra" y de "atr_period_" sean válidos.
//+------------------------------------------------------------------+ //| 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; }
Si alguno es inválido, removemos el asesor experto del gráfico actual.
Si "type_extra" es "EXTRA_BE_RRR_BY_ATR", creamos el handle del indicador atr con los parámetros "tf_atr" y "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); }
Finalmente, llamamos a la primera función "SetSimple".
SetSimple(rr_a_put_the_break_even, type_extra, atr_multiplier_or_extra_points, idx_atr_, this.handle_atr);
} Ahora, sobrescribiremos la función .Set(), que sirve para establecer los parámetros de la clase y que hereda de la clase base de breakeven.
void Set(MqlParam ¶ms[]) override;
Como en la función Set, primero verificamos que el tamaño del array "params" no sea menor que el número de parámetros requeridos por la clase.
//+------------------------------------------------------------------+ //| 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; }
Si la verificación falla, removemos el asesor experto.
Para usar el array "params" y establecer los valores internos de la clase, usamos él define "HANDLE_INSTEAD_OF_PERIOD". Este define indicará si los parámetros en "params" ya tienen el handle definido. Si es así, usaremos la primera función "SetSimple"; si no, llamaremos a la segunda función "SetSimple", que crea el handle.
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); } }
Función .Add
Dentro de la función "Add" nos encargamos de establecer los valores que la clase "CBreakEvenBase" requiere para poder ejecutar la función "CBreakEven", que se encargará de aplicar el breakeven a las posiciones abiertas.
Primero verificamos que la posición tenga un stoploss configurado.
//+------------------------------------------------------------------+ //| 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; }
Si no tiene stoploss, retornamos false y no se añadirá al array de posiciones "PositionsBe".
Luego creamos una variable double llamada val que almacenará la distancia desde el precio de apertura donde se ubicará el precio del stoploss. Si el "type" tiene el valor "EXTRA_BE_RRR_BY_ATR", copiamos los datos del atr empezando desde la variable interna "idx_atr", copiando solo un elemento. Si se copia menos de un elemento, retornamos false e imprimimos un mensaje de error. De lo contrario, multiplicamos val por el valor en índice 0 de "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]; }
Luego verificamos que la distancia donde se pondrá el stoploss sea válida, es decir, que sea menor que la distancia donde se activará el breakeven, para evitar errores como 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; }
Finalmente, si se superan todas las comprobaciones, calculamos el nuevo precio del stoploss "breakeven_price" y el precio que debe superarse para activar el breakeven "price_to_beat", usando las siguientes fórmulas:
Para el nuevo stoploss "breakeven_price".
- Posiciones de compra:
break_even_price = open_price + val
- Posiciones de venta:
break_even_price = open_price - val
Para el precio a superar "price_to_beat".
- Posiciones de compra:
price_to_beat = open_price + (coefficient_rr * diff)
- Posiciones de venta:
price_to_beat = open_price - (coefficient_rr * diff)
Creamos una estructura "position_be" y le asignamos los valores necesarios: tipo de posición, ticket y los dos precios ("breakeven_price" y "price_to_beat").
Finalmente, agregamos esta estructura al array "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; }
Clase CBreakEven: selección y configuración dinámica de un único tipo de breakeven
Para elegir eficientemente el tipo de breakeven que se usará, crearemos una clase tipo seleccionadora. Esta se encargará de ejecutar el breakeven y convertir un puntero interno a un tipo derivado, basado en el tipo de breakeven seleccionado por el usuario.
Empezamos definiendo la estructura "BreakEvenParams", que contendrá un array del tipo MqlParam donde se guardarán los parámetros de un tipo de breakeven.
struct BreakEvenParams { MqlParam params[]; };
Ahora declaramos la clase en sí y la parte privada.
//--- class CBreakEven { private: ulong magic; string symbol; ENUM_BREAKEVEN_MODE breakeven_mode; BreakEvenParams parameters[]; CBreakEvenBase* CreateBreakEven(ENUM_BREAKEVEN_TYPE type);
La parte privada contiene el número mágico, el símbolo y el modo del breakeven. Además, tiene un array del tipo "BreakEvenParams" para guardar los parámetros de los tres tipos de breakeven que se considerarán. También se declara una función que devuelve un puntero del tipo CBreakEvenBase, la cual será llamada en una función posterior para establecer un puntero público.
Finalmente, creamos una variable del tipo CBreakEvenBase*, que será ajustada y casteada en la función "SetInternalPointer", según el tipo de breakeven seleccionado por el usuario.
CBreakEvenBase* obj;
Constructor
El constructor tendrá los mismos parámetros que el constructor de la clase base de breakeven.
CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode);
Dentro del constructor inicializaremos los valores de las variables internas y estableceremos el tamaño del array de parámetros en 3, que es el número total de parámetros.
//+------------------------------------------------------------------+ //| Contructor | //+------------------------------------------------------------------+ CBreakEven::CBreakEven(ulong magic_, string symbol_, ENUM_BREAKEVEN_MODE mode) { this.magic = magic_; this.symbol = symbol_; this.breakeven_mode = mode; ArrayResize(parameters, 3); }
Destructor
Dentro del destructor primero liberaremos el array "parameters".
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CBreakEven::~CBreakEven() { ArrayFree(this.parameters);
Antes de eliminar el puntero de breakeven "obj", verificaremos que sea dinámico y, si es así, lo eliminaremos.
if(CheckPointer(this.obj) == POINTER_DYNAMIC) { delete obj; obj = NULL; } }
Funciones para establecer parámetros
Para establecer los valores dentro del array de parámetros, crearemos cinco funciones, que permitirán configurar los parámetros de los tres tipos de breakeven. Dos para el atr, dos para rr, y una para breakeven por puntos fijos.
Funciones para establecer los parámetros para el breakeven por RR
Comenzamos con las funciones para rr. Como mencioné, serán dos: una que requiere un handle de 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_);
Y otra que requiere los parámetros generales y los parámetros para crear el handle del 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_);
Empezamos definiendo la primera función, que requiere el handle del atr. En esta función, como en las demás, comenzamos ajustando el tamaño del array "params" a un valor de 6.
ArrayResize(parameters[int(BREAKEVEN_TYPE_RR)].params, 6);
Luego establecemos cada índice del array "params" con el valor que corresponde.
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_;
El último índice, el 5, tendrá el valor "HANDLE_INSTEAD_OF_PERIOD", para que la función para establecer parámetros de la clase "CBreakEvenRR" sepa qué función llamar; en este caso, la que requiere un handle.
parameters[BREAKEVEN_TYPE_RR].params[5].integer_value = HANDLE_INSTEAD_OF_PERIOD; Para la segunda función, los pasos son similares, pero ahora el índice 4, que antes tenía el valor del handle del atr, tendrá el timeframe para el atr, y el índice 5 tendrá el período del 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_; }
Código completo.
//+------------------------------------------------------------------+ //| 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; }
Funciones para establecer los parámetros para el breakeven por atr
Para establecer los valores que tendrá el array "params", usaremos también dos funciones, como en el breakeven por rr, pero con menos parámetros, en total cinco.
Las dos funciones para establecer parámetros tendrán exactamente cinco parámetros. La primera, que requiere un handle, la definiremos así.
void CBreakEven::SetBeByAtr(int atr_idx, double atr_multiplier_be, double atr_multiplier_extra_be, int atr_handle)
En su declaración, empezamos estableciendo el tamaño del array de parámetros dentro del array "params".
ArrayResize(parameters[int(BREAKEVEN_TYPE_ATR)].params, 5);
Luego, asignamos los valores a los cinco elementos del array "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;
Recordemos que el último elemento, índice 4, debe tener el valor "HANDLE_INSTEAD_OF_PERIOD", para que la función miembro de "CBreakEvenAtr" que requiera el handle pueda ser elegida.
parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = HANDLE_INSTEAD_OF_PERIOD;
Para la segunda función, lo único que cambia son los valores en los índices 3 y 4.
El índice 3 tendrá el valor del timeframe del atr.
parameters[BREAKEVEN_TYPE_ATR].params[3].integer_value = int(timeframe);
Y el índice 4 tendrá el valor del período del atr.
parameters[BREAKEVEN_TYPE_ATR].params[4].integer_value = atr_period;
Código completo.
//+------------------------------------------------------------------+ //| 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; }
Función para establecer los parámetros para el breakeven por puntos fijos
Para finalizar los seteos, queda el breakeven por puntos, que únicamente requiere dos parámetros. Dentro de la clase "CBreakEven" la declararemos así.
void SetBeByFixedPoints(int points_be_, int extra_points_be_);
Para definirla, empezamos estableciendo el tamaño del array "params" correspondiente a 2.
ArrayResize(parameters[int(BREAKEVEN_TYPE_FIXED_POINTS)].params, 2);
Luego asignamos el valor del índice 0 a los puntos del breakeven y el índice 1 a los puntos extra.
parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[0].integer_value = points_be_; parameters[BREAKEVEN_TYPE_FIXED_POINTS].params[1].integer_value = extra_points_be_;
Código completo
//+------------------------------------------------------------------+ //| 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_; }
Función para obtener el puntero basándonos en el tipo de breakeven
Para obtener el tipo de breakeven necesario según el tipo dado por el usuario, crearemos una función que devolverá un puntero a "CBreakEvenBase".
CBreakEvenBase *CBreakEven::CreateBreakEven(ENUM_BREAKEVEN_TYPE type)
Empezamos creando un switch donde cada caso corresponde a un tipo de breakeven. Si la variable type no coincide con ninguno, el caso por defecto retornará NULL. Si coincide con alguno, retornará un puntero a la clase hija correspondiente.
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; }
Código completo
//+------------------------------------------------------------------+ //| 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; }
Función para establecer un valor interno al puntero "obj"
Finalmente, para asignar un valor al puntero interno crearemos una función llamada "SetInternalPointer" que requerirá como parámetro el tipo de breakeven:
void SetInternalPointer(ENUM_BREAKEVEN_TYPE type) Definiremos la función empezando por verificar el estado del puntero "obj".
if(CheckPointer(this.obj) == POINTER_DYNAMIC) { delete this.obj; this.obj = NULL; }
Si el puntero es dinámico, lo eliminamos.
Luego, el puntero "obj" será inicializado con la función "CreateBreakEven", que devuelve un puntero basándonos en el tipo de breakeven. Después de asignarlo, comprobamos si es NULL. En ese caso, imprimimos un mensaje de error y removemos el asesor del gráfico actual.
this.obj = CreateBreakEven(type); if(this.obj == NULL) { printf("%s: Critical error | The type %d is invalid.", __FUNCTION__, type); ExpertRemove(); return; }
Comprobamos que el tamaño del array "params" sea suficiente para contener los parámetros necesarios. Si esta comprobación falla, eliminamos el objeto, establecemos el puntero a NULL y removemos el asesor del gráfico.
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; }
Finalmente, establecemos los valores necesarios usando la función "Set" de la clase base.
obj.Set(parameters[type].params);
Código completo.
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Backtesting del bot de order blocks con distintos tipos de breakeven
En esta última sección nos dedicaremos a probar los tres modos de breakeven en el probador de estrategias.
Comenzaremos con las configuraciones de los parámetros. Usaremos un stoploss y takeprofit basados en atr. El stoploss tendrá un atr multiplier de 3.0 y el takeprofit de 6.0, es decir, una relación 1:2. El horario de operaciones será desde las 3 a.m. hasta las 14 p.m. GMT-3.
Configuración.

Imagen 1: Configuraciones para el backtest del bot de order blocks
Como se observa en la imagen, el bot será probado en oro (XAUUSD) y seleccionaremos una temporalidad de M5 (5 minutos). El modelo será a base de ticks reales, con un apalancamiento de 1:30 y un depósito inicial de 10,000 USD.
Backtest inicial sin breakeven
Para poder hacer una comparación más clara, este primer backtest se realizará sin el breakeven activado.

Imagen 2: Gráfico del backtest del bot de order blocks sin breakeven
Antes de comenzar con los backtests, hay que tener en cuenta que los resultados obtenidos son ciertos para parámetros específicos. Por lo tanto, las conclusiones que se presenten no pueden generalizarse a cualquier estrategia o conjunto de parámetros. Sin embargo, podemos hacer una aproximación para valores similares.
Por ejemplo, en el siguiente backtest que se presentará, probaremos el breakeven por puntos fijos. Como parámetros tendremos 100 puntos extra para el precio del breakeven y 200 puntos para activar el breakeven. Por lo tanto, las conclusiones que saquemos luego del backtest estarán relacionadas con valores cercanos a 200 y 100 puntos para los puntos para activar el breakeven y los puntos extra, respectivamente.
Backtest con el breakeven por puntos
Para el backtest por puntos fijos eligiremos 200 puntos para que se active el breakeven y como puntos extra 100 puntos, estos han sido los resultados.

Imagen 3: Gráfico del bot de order blocks con breakeven por puntos fijos
Como se observa en la imagen, hubo muchas rachas de posiciones con poco beneficio. Estas fueron afectadas por el breakeven, como era de esperarse. Además, las posiciones que tuvieron flotante positivo, aproximadamente entre el 16 de abril y el 29 de abril de 2025, en este backtest con breakeven por puntos fijos, cerraron sin pérdidas. En cambio, en el primer backtest hubo pérdidas.
Esto podría indicar que el breakeven por puntos es más conservador, pero también puede limitar las ganancias futuras. Como resultado, en este backtest el balance final fue de solo 12,000 USD, mientras que en el primer backtest el balance final fue aproximadamente 15,800 USD. Esto sugiere que el breakeven por puntos fijos, aunque limita pérdidas, también restringe las ganancias.
Backtest con breakeven por atr

Imagen 4: Gráfico del bot de order blocks con breakeven por atr
En este segundo backtest con breakeven por atr, se observa una mejora en la gráfica. Principalmente, analizaremos el efecto de este breakeven en rachas positivas y negativas.
En las rachas positivas, el breakeven no actúa tan agresivamente como lo hacía el breakeven por puntos. En las rachas negativas, limita moderadamente las pérdidas. Esto se puede ver en la primera gran racha de pérdidas que se tuvo a comienzos de 2024 hasta marzo. La curva del balance de este backtest es parecida a la del primer backtest.
Por lo tanto, podemos concluir que este breakeven es más moderado, quizá no tan agresivo como el de puntos fijos, ya que dependiendo de la volatilidad, el precio para activarse puede ser más alto o más bajo, según el valor del atr.
Backtest con breakeven por rr

Imagen 5: Gráfico del bot de order blocks con breakeven por rr, con una relación stoploss-takeprofit de 1:2.
En este tercer backtest se observa que en las rachas perdedoras, especialmente en enero y febrero de 2024, no hay mucha diferencia entre el backtest sin breakeven y el breakeven por rr. Esto sugiere que esas pérdidas fueron reales, es decir, no hubo flotante positivo; una vez abierta la posición, fue directo al stoploss, por lo que el breakeven por rr no tuvo oportunidad de activarse.
En las rachas positivas, se limita moderadamente el avance de las ganancias. Por ejemplo, en julio de 2024, donde en el primer backtest casi todas las posiciones cerraron en takeprofit, con el breakeven por rr esa racha se estancó. Esto podría deberse a que el bot operó en rangos de consolidación, donde el precio no formó una tendencia clara, lo que permitió que el breakeven se activara y cerrara la operación antes de que la tendencia tomara dirección.
Podemos concluir que el breakeven por rr es más conservador, pues requiere más margen para activarse. Por eso no es el mejor candidato para estrategias agresivas, pero puede ser útil para estrategias tipo swing que busquen un ratio riesgo-beneficio alto, como 1:3 o 1:5. Para comprobar esto, a continuación realizaremos un backtest con una relación riesgo-beneficio de 1:3.

Imagen 6: Gráfico del bot de order blocks con breakeven por rr, con una relación stoploss-takeprofit de 1:3.
Este nuevo backtest es mejor que el anterior con ratio 1:2. Los principales cambios son que el balance mínimo es de aproximadamente 9,500 USD, casi igual que en el breakeven por atr. Además, durante la primera racha perdedora entre enero y febrero, las pérdidas no son tan agresivas como en el backtest original.
Sin embargo, en abril la racha perdedora es la más larga de los tres tipos de breakeven, casi cinco meses en pérdidas. Esto coincide con un período de lateralización del oro de casi cinco meses. Comparado con el primer backtest para breakeven por rr, esto podría indicar que las posiciones no se cerraron rápidamente, sino que probablemente tuvieron fluctuaciones entre flotante negativo y positivo, por lo que el tamaño del stoploss y takeprofit requería ser considerable.
El problema es que en este nuevo backtest el tamaño del stoploss se redujo a un tercio del original, por lo que era más probable que las posiciones cerraran en pérdidas. Esto podría indicar que, durante el período de lateralización, el tiempo de duración de las operaciones fue mayor y que antes de cerrar hubo fluctuaciones entre valores positivos y negativos.
Conclusión
En este artículo terminamos la implementación del breakeven por atr y rr en MQL5. Luego creamos una clase, que permite manejar diferentes tipos de breakeven en una sola clase, facilitando el cambio entre ellos sin necesidad de volver a ingresar los parámetros para un nuevo breakeven.
Finalmente, probamos y analizamos cada tipo de breakeven. Vimos que el breakeven por puntos fijos, con los parámetros dados, es más agresivo. Por el contrario, el breakeven por rr es más pasivo, pero penaliza más las ganancias. El breakeven por atr se ubica como un punto medio entre estos dos.
Para concluir, menciono que no podemos afirmar con exactitud cuál tipo de breakeven es mejor para cada estrategia, pues esto depende de varios factores que hay que considerar. Esto sería más una tarea de análisis personalizado. Sin embargo, una sugerencia es probar qué tipo de breakeven se adapta mejor a la estrategia.
Por ejemplo, en los tres backtests vimos que posiblemente el breakeven por rr funciona mejor para estrategias con un rr definido mayor o igual a 1:3. Esto lo comprobamos en el quinto backtest, donde el beneficio neto fue casi tres veces mayor que en el cuarto backtest.
Archivos usados/mejorados en este artículo:
| Nombre del archivo | Tipo | Descripción |
|---|---|---|
| Risk_Management.mqh | .mqh (archivo de cabecera) | Contiene la clase de gestión de riesgo desarrollada en el último artículo de gestión de riesgo. |
| Order_Block_Indicador_New_Part_2.mq5 | .mq5 (indicador) | Contiene el código del indicador de order blocks. |
| Order Block EA MT5.mq5 | .mq5 (asesor experto) | Código del bot de Order Block con el breakeven integrado. |
| OB_SET_WITHOUT_BREAKEVEN.set | .set (archivo de configuración) | Configuraciones del primer backtest, sin breakeven. |
| OB_SET_BREAKEVEN_POINTS.set | .set (archivo de configuración) | Configuraciones del segundo backtest, breakeven por puntos fijos. |
| OB_SET_BREAKEVEN_ATR.set | .set (archivo de configuración) | Configuraciones del tercer backtest, breakeven por atr. |
| OB_SET_BREAKEVEN_RR_1_2.set | .set (archivo de configuración) | Configuraciones del cuarto backtest, breakeven por rr, con un rr 1:2. |
| OB_SET_BREAKEVEN_RR_1_3.set | .set (archivo de configuración) | Configuraciones del quinto backtest, breakeven por rr, con un rr 1:3. |
| PositionManagement.mqh | .mqh (archivo de cabecera) | Archivo mqh que contiene el código del breakeven. |
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Funciones de activación neuronal durante el aprendizaje: ¿la clave de una convergencia rápida?
Del básico al intermedio: Estructuras (V)
Cliente en Connexus (Parte 7): Añadir la capa de cliente
Características del Wizard MQL5 que debe conocer (Parte 48): Bill Williams Alligator
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso