
Gestión de Riesgo (parte 3): Construyendo la Clase Principal para la Gestión de Riesgo
- Introducción
- Diseño y planificación de la gestión de riesgo
- Definición de constantes, enumeraciones y estructuras
- Declaración de variables clave para la gestión de riesgo
- Creación del constructor, destructor y métodos de inicialización
- Métodos para asignación de valores a pérdidas y ganancias
- Cálculo del lote y stop loss basado en el riesgo por operación
- Funciones para obtener el valor de pérdidas, ganancias y riesgo
- Eventos programados para nuevo día y nueva semana
- Conclusión
Introducción
Bienvenido a nuestro artículo. Esta entrega es la continuación de la serie en la que estamos desarrollando un sistema de gestión de riesgos. En el artículo anterior, construimos una interfaz aplicando los conceptos aprendidos en la primera parte. Ahora, en esta tercera parte, nos enfocaremos en completar la estructura principal de la clase de gestión de riesgos.
En este artículo, crearemos una clase que nos permitirá asignar valores a pérdidas y ganancias, estableciendo así las bases para el cálculo y seguimiento de los beneficios (profits). Con esto, daremos un paso clave en la construcción de un sistema sólido y funcional para la gestión de riesgos.
En el esquema mostrado, se plantea un plan estructurado para el diseño y planificación de la gestión de riesgos en nuestro sistema.
-
Definiciones, estructuras y enumeraciones
Lo primero que haremos será definir las estructuras y enumeraciones necesarias. Estas serán fundamentales para almacenar información clave, como las pérdidas y ganancias acumuladas, y facilitar el manejo de datos dentro del sistema. -
Creación de la clase CRiskManagement
Luego, desarrollaremos la clase principal encargada de la gestión del riesgo CRiskManagement. Esta clase centralizará todos los cálculos y procesos relacionados con el control del riesgo, asegurando una implementación organizada y eficiente. -
Funciones esenciales: asignación y obtención de valores
Posteriormente, implementaremos las funciones necesarias para asignar y obtener valores, lo que nos permitirá actualizar y consultar la información de pérdidas y ganancias. Además, en esta etapa, definiremos el constructor y el destructor de la clase para gestionar correctamente la memoria y la inicialización de los datos. -
Funciones activadas por eventos
Finalmente, desarrollaremos funciones que se ejecutarán en momentos clave, como al inicio de un nuevo día o semana. Estas funciones serán útiles para recalcular beneficios, ajustar riesgos y reinicializar variables acumuladas en ciertos períodos, asegurando un seguimiento adecuado del rendimiento a lo largo del tiempo.
Ahora que hemos definido nuestro plan de acción, comenzaremos desde la base.
Definición de constantes, enumeraciones y estructuras
Antes de comenzar con la implementación del código, organizaremos toda la lógica relacionada con la gestión de riesgos en un archivo independiente llamado "Risk_Management.mqh", (El archivo base que creamos). De esta manera, mantendremos un código más estructurado y modular.
Siguiendo nuestro esquema de gestión de riesgos, el primer paso será la creación de estructuras, enumeraciones y constantes definidas. Estos elementos serán fundamentales para múltiples funciones, principalmente aquellas encargadas de calcular y controlar valores como la pérdida máxima diaria, semanal o por operación.
Definición de constantes (#define)
Para facilitar la gestión de riesgos y mejorar la legibilidad del código, definiremos varias constantes clave:
1. NOT_MAGIC_NUMBER:
Esta constante se utilizará como valor de inicialización en el constructor de la clase. Su propósito es indicar que no se empleará un número mágico específico, sino que la gestión de riesgos se aplicará a todas las operaciones sin restricción de un identificador en particular. Esto puede ser útil si se desea implementar la gestión de riesgos con respecto a cuenta completa en un entorno de trading en vivo.
#define NOT_MAGIC_NUMBER 0 //Not Magic Number
2. Banderas (FLAGS) para el cierre de operaciones:
Para hacer más dinámica la función de cierre de posiciones, utilizaremos banderas en lugar de crear múltiples funciones separadas para cerrar operaciones con ganancias, pérdidas o ambas. Estas banderas permitirán simplificar el código y mejorar su reutilización.
#define FLAG_CLOSE_ALL_PROFIT 2 //Flag indicating to close only operations with profit #define FLAG_CLOSE_ALL_LOSS 4 //Flag indicating to close only operations without profit
Enumeraciones
1. Método de cálculo: Monto fijo o porcentajePara empezar, necesitaremos una enumeración que nos permita determinar cómo se calcularán las pérdidas y ganancias máximas dentro de la estrategia. Existen dos enfoques principales:
-
Fijo (money): El usuario establece un monto específico en dinero para definir la pérdida o ganancia máxima. Este valor se mantiene constante, independientemente del saldo de la cuenta.
- Ejemplo: Si se fija una pérdida máxima diaria de 1000 USD, este límite permanecerá sin cambios mientras el sistema esté en funcionamiento.
-
Dinámico (percentage): En lugar de un valor fijo, el usuario selecciona un porcentaje que se aplicará sobre un parámetro de referencia dentro de la cuenta, como el balance o la equidad.
- Ejemplo: Si se establece un límite del 2% del balance, la pérdida máxima variará en función del saldo de la cuenta. Si el balance crece, el límite de pérdida aumentará proporcionalmente; si el balance disminuye, el límite también se reducirá.
En código, esto se representa mediante la siguiente enumeración:
enum ENUM_RISK_CALCULATION_MODE //enumeration to define the types of calculation of the value of maximum profits and losses { money, //Money percentage //Percentage % };
2. Aplicación del porcentaje: ¿Sobre qué parámetro se calculará el riesgo?
Si se elige el método de cálculo por porcentaje, es necesario definir sobre qué valor de la cuenta se aplicará dicho porcentaje. Para ello, consideramos cuatro opciones clave:
- Balance (Balance): Se aplica el porcentaje sobre el saldo total de la cuenta.
- Ganancia neta (ganancianeta): Se basa en el beneficio acumulado desde la creación de la cuenta.
- Margen libre (free_margin): Se calcula sobre el capital disponible para abrir nuevas operaciones.
- Equidad (equity): Representa el balance ajustado por las ganancias o pérdidas de las posiciones abiertas.
enum ENUM_APPLIED_PERCENTAGES //enumeration to define the value to which the percentages will be applied { Balance, //Balance ganancianeta,//Net profit free_margin, //Free margin equity //Equity };
3. Tipo de gestión de riesgos: Cuenta personal o Prop Firm (FTMO)
En capítulos anteriores mencionamos que este sistema de gestión de riesgos puede aplicarse tanto a cuentas personales como a Prop Firms, específicamente FTMO. Sin embargo, en FTMO el cálculo de la pérdida máxima diaria es más dinámico y sigue un enfoque diferente.
Más adelante, cuando analicemos las estructuras, explicaremos en detalle cómo FTMO maneja este cálculo. Por ahora, crearemos una enumeración que permita seleccionar el tipo de cuenta para la gestión de riesgos.
enum ENUM_MODE_RISK_MANAGEMENT //enumeration to define the type of risk management { propfirm_ftmo, //Prop Firm FTMO personal_account // Personal Account };
4. ¿Qué tipo de lote se usará?
No todos los traders prefieren operar con un lote dinámico, por lo que permitiremos que el usuario elija el tipo de lote que desea utilizar. Esta elección no afectará la lógica central de la clase de gestión de riesgo, pero es importante al momento de desarrollar un EA, ya que debemos dar al usuario la opción de operar con un lote fijo o dinámico.
Para esto, crearemos la siguiente enumeración:
enum ENUM_LOTE_TYPE //lot type { Dinamico,//Dynamic Fijo//Fixed };
Al abrir una operación, podremos condicionar la selección del lote de la siguiente manera:
trade.Sell( (Lote_Type == Dinamico ? risk.GetLote(ORDER_TYPE_SELL,GET_LOT_BY_ONLY_RISK_PER_OPERATION) : lote), _Symbol,tick.bid,sl,tp,"EA Sell");
En este fragmento de código, si el usuario elige un lote dinámico (Dinamico), la función GetLote calculará el tamaño del lote basándose en el riesgo por operación. En cambio, si elige un lote fijo (Fijo), el EA usará el valor predeterminado asignado a la variable lote.
Más adelante, crearemos la función GetLote para obtener el lote dinámico de manera adecuada.
5. ¿Cómo se calculará el lote si es dinámico?
Si el usuario elige operar con un lote dinámico, debemos definir cómo se calculará su tamaño. Para esto, crearemos una enumeración que determine si el cálculo se basará únicamente en el riesgo por operación o si también se ajustará en función del stop loss.
-
Cálculo basado solo en el riesgo por operación:
Se determina el lote considerando únicamente el porcentaje de riesgo por operación. No se necesita establecer un valor específico para el stop loss en este caso. -
Cálculo basado en el riesgo por operación y el stop loss
En este método, además de calcular el lote con base en el riesgo por operación, se ajusta en función del stop loss. Esto es importante porque si definimos un porcentaje de riesgo fijo sin ajustar el stop loss, podríamos terminar usando un tamaño de lote inadecuado.Ejemplo:
Supongamos que queremos arriesgar el 1% de una cuenta de $1000, lo que equivale a un riesgo máximo de $10. Si nuestra estrategia tiene una relación de riesgo-beneficio de 1:2 y usamos un stop loss de 100 puntos en el EUR/USD (donde cada punto vale 0.00001 en un broker de 5 decimales), podríamos calcular incorrectamente un lote de 0.02.- Si la operación da como resultado pérdida, con un stop loss de 100 puntos perderíamos solo $2 en lugar de los $10 previstos.
- Si la operación es exitosa, ganaríamos solo $4 en lugar de los $20 esperados con la relación 1:2.
Para solucionar esto, ajustamos el tamaño del lote de manera que, con el stop loss definido, la pérdida máxima sea exactamente el 1% de la cuenta. Esto garantiza que si la operación llega al take profit con un ratio 1:2, la ganancia sea del 2%.
La función GetIdealLot, presentada en un artículo anterior, realiza este ajuste de forma automática.
void GetIdealLot(double& nlot, double glot, double max_risk_per_operation, double& new_risk_per_operation, long StopLoss)
Esta función ajusta el lote para que el riesgo por operación se mantenga dentro del límite especificado.
Para representar estos dos métodos de cálculo, definimos la siguiente enumeración:enum ENUM_GET_LOT { GET_LOT_BY_ONLY_RISK_PER_OPERATION, //Obtain the lot for the risk per operation GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION //Obtain and adjust the lot through the risk per operation and stop loss respectively. };
Estructuras:
Las estructuras juegan un papel clave en nuestro sistema de gestión de riesgos. Como mencionamos anteriormente, las pérdidas y ganancias se calculan con base en un método: directo (monto fijo) o dinámico (porcentaje). En caso de usar el método basado en porcentaje, este debe aplicarse sobre un valor de referencia dentro de la cuenta.
Para ello, crearemos una estructura que contenga toda la información necesaria para definir una pérdida o ganancia.
- value: Representa el monto de la pérdida o ganancia.
- assigned_percentage: Indica el porcentaje asignado en caso de que el cálculo sea dinámico.
- mode_calculation_risk: Define el método de cálculo del riesgo por operación (directo o dinámico).
- percentage_applied_to: Variable de tipo enumeración que determina sobre qué métrica de la cuenta se aplicará el porcentaje en caso de usar el método dinámico.
Representada en codigo:
struct Loss_Profit { double value; //value double assigned_percentage; //percentage to apply ENUM_RISK_CALCULATION_MODE mode_calculation_risk; //risk calculation method ENUM_APPLIED_PERCENTAGES percentage_applied_to; //percentage applied to };
Con esta estructura, definiremos las siguientes variantes de pérdida y ganancia:
Nombre | Descripción | Reinicio |
---|---|---|
Pérdida máxima diaria (MDL) | Es la pérdida máxima permitida en un solo día. | Se reinicia diariamente a 0. |
Pérdida máxima (ML) | Es la pérdida total máxima que puede alcanzar la cuenta durante toda su existencia. | No se reinicia. |
Pérdida máxima por operaciones (GMLPO) | Similar al riesgo por operación definido por el usuario, se utiliza para estimar el tamaño de lote adecuado. | Se recalcula cada vez que se cierra una operación o al inicio de un nuevo día. |
Pérdida máxima semanal (MWL) | Es la pérdida máxima permitida en una semana. Es opcional, ya que no es un requisito en prop firms. | Se reinicia semanalmente. |
Pérdida máxima neta por operaciones (NMLPO) | Se genera únicamente cuando se llama a la función que calcula el lote en función del riesgo por operación y el stop loss. | Se recalcula cada vez que se llama a la función de obtención de lote basada en riesgo y stop loss. |
Ganancia máxima diaria | Es la ganancia máxima que el bot o la cuenta pueden obtener en un solo día. | Se reinicia diariamente. |
Particularidades de la pérdida máxima diaria en FTMO
Cuando adaptamos esta gestión de riesgos para cuentas normales y prop firms como FTMO, surgen algunas diferencias clave. En FTMO, la pérdida máxima diaria es superdinámica, ya que puede aumentar si hay ganancias durante el día:
For example, in the case of an FTMO Challenge with the initial account balance of $200,000, the Max Daily Loss limit is $10,000. If you happen to lose $8,000 in your closed trades, your account must not decline more than $2,000 this day. It must also not go -$2,000 in your open floating losses. The limit is inclusive of commissions and swaps.
Vice versa, if you profit $5,000 in one day, then you can afford to lose $15,000, but not more than that. Once again, be reminded that your Maximum Daily Loss counts your open trades as well. For example, if in one day, you have closed trades with a loss of $6,000 and then you open a new trade that goes into a floating loss of some -$5,700 but ends up positive in the end, unfortunately, it is already too late. In one moment, your daily loss was -$11,700 on the equity, which is more than the permitted loss of $10,000.
Como se puede observar en la nota extraída directamente de la página de FTMO, la pérdida máxima diaria puede variar en función de las ganancias obtenidas durante el día.
Por ejemplo, si al inicio del día tu pérdida máxima permitida es de 10,000 USD y generas una ganancia ese mismo día, esta ganancia se sumará a los 10,000 USD iniciales, aumentando así tu margen de pérdida permitido. De esta manera, a medida que acumules ganancias, la cantidad que puedes arriesgar también se ajustará en consecuencia.
Declaración de variables clave para la gestión de riesgo
En esta sección, definiremos la clase CRiskManagement, encargada de gestionar el riesgo de las operaciones.
//+------------------------------------------------------------------+ //| Class CRisk Management | //+------------------------------------------------------------------+ class CRiskManagemet final
Inclusión de librerías
Antes de definir la clase, incluiremos la librería CTrade.mqh para poder gestionar operaciones, como el cierre de posiciones:
#include <Trade/Trade.mqh>
1. Puntero a CTrade
Como primera variable, crearemos un puntero a la clase CTrade, lo que nos permitirá interactuar con las posiciones abiertas:
private: //--- CTrade class pointer to be able to work with open positions CTrade *trade;
2. Variables principales
Definiremos tres variables principales:
- account_profit (double): Almacena la ganancia neta de la cuenta desde 1971.01.01 (la fecha mínima en datetime).
- StopLoss (long): Guarda el nivel de stop loss en puntos, utilizado para calcular el tamaño del lote.
- lote (double): Variable interna que almacena el último lote calculado.
Código:
//-- Main variables double account_profit; long StopLoss; double lote;
3. Variables generales
- magic_number (ulong): Almacena el número mágico, utilizado para gestionar el riesgo vinculado a un conjunto específico de operaciones. También puede ser 0, lo que permite su uso en cuentas personales.
- mode_risk_management (ENUM_MODE_RISK_MANAGEMENT): Determina el tipo de gestión de riesgo que se aplicará, ya sea para cuentas normales o prop firms (como FTMO).
Código:
//--- General variables ENUM_MODE_RISK_MANAGEMENT mode_risk_managemet; ulong magic_number;
4. Variables específicas para cuentas de prop firms (FTMO)
En cuentas de prop firms, las reglas de gestión de riesgo se aplican sobre el balance inicial y no sobre el balance actual. Por ejemplo, la pérdida máxima diaria y la pérdida máxima total suelen calcularse como un % del saldo inicial (por lo general, un 10%).
Para manejar esta particularidad, agregaremos una variable para almacenar el balance inicial, la cual deberá ser establecida manualmente por el usuario:
//--- Variables to work with anchoring tests double account_balance_propfirm;
5. Variable para la pérdida máxima estimada en la siguiente operación (NMLPO)
Esta variable almacenará la pérdida máxima estimada en la siguiente operación. No es obligatoria, pero puede ser útil para predecir cuánto perdería el bot si la posición llegara al Stop Loss.
Se calculará solo cuando se llame a la función: GetLote()
//--- Variables to store the values of the maximum losses double nmlpo;
6. Variables para gestión de pérdidas y ganancias
Se utilizará la estructura Loss_Profit (definida previamente) para almacenar tanto el valor de la pérdida como su método de cálculo y la forma en que se aplica el porcentaje.
Definiremos cinco variables clave:
//--- variables that store percentages and enumeration, which will be used for the subsequent calculation of losses
Loss_Profit mdl, mwl, ml, gmlpo, mdp;
Donde:
- mdl: Pérdida máxima diaria (Max Daily Loss).
- mwl: Pérdida máxima semanal (Max Weekly Loss).
- ml: Pérdida máxima total (Max Loss).
- gmlpo: Pérdida máxima por operación (Gross Max Loss Per Operation).
- mdp: Máxima ganancia diaria (Máximum daily profit).
7. Variables para obtener el profit (Diario, Semanal y Total)
Para monitorear el desempeño del bot/EA, agregaremos variables que almacenen las ganancias y permitan alertar sobre límites de pérdida o ejecutar acciones de seguridad.
Necesitaremos:
- Tiempo de referencia desde el cual calcular el profit.
- Variables para almacenar el profit.
Código:
//--- Variables to store the profit and the time from which they will be obtained double daily_profit, weekly_profit, gross_profit; datetime last_weekly_time, last_day_time, init_time;
Donde:
- daily_profit: Ganancia obtenida en el día.
- weekly_profit: Ganancia obtenida en la semana.
- gross_profit: Ganancia total desde el inicio de la gestión de riesgo.
- last_weekly_time: Última fecha registrada para calcular el profit semanal.
- last_day_time: Última fecha registrada para calcular el profit diario.
- init_time: Fecha de inicio de la gestión de riesgo.
Creación del constructor, destructor y métodos de inicialización
Ahora que hemos definido las variables clave, implementaremos las funciones principales de la clase CRiskManagement.
Constructor
El constructor se encargará de inicializar las variables principales, como el número mágico, el modo de gestión de riesgo y el balance de la cuenta de una prop firm (si aplica).
La declaración del constructor será la siguiente:
CRiskManagemet(ulong magic_number_ = NOT_MAGIC_NUMBER,ENUM_MODE_RISK_MANAGEMENT mode_risk_management_ = personal_account, double account_propfirm_balance=0);
Dentro de la implementación del constructor, asignaremos valores a las variables, inicializaremos el objeto CTrade y calcularemos la ganancia neta de la cuenta desde el 1 de enero de 1972. También estableceremos las marcas de tiempo relevantes para el seguimiento de las ganancias.
CRiskManagemet::CRiskManagemet(ulong magic_number_ = NOT_MAGIC_NUMBER,ENUM_MODE_RISK_MANAGEMENT mode_risk_management_ = personal_account, double account_propfirm_balance =0) { if(magic_number_ == NOT_MAGIC_NUMBER) { Print("| Warning | No magic number has been chosen, taking into account all the magic numbers and the user's trades"); } //--- this.account_balance_propfirm = account_propfirm_balance ; trade = new CTrade(); this.account_profit = GetNetProfitSince(true,this.magic_number,D'1972.01.01 00:00'); this.magic_number = magic_number_; this.mode_risk_managemet = mode_risk_management_; //--- this.last_day_time = iTime(_Symbol,PERIOD_D1,0); this.last_weekly_time = iTime(_Symbol,PERIOD_W1,0); this.init_time =magic_number_ != NOT_MAGIC_NUMBER ? TimeCurrent() : D'1972.01.01 00:00'; }
Nota: Si el usuario no define un número mágico, se imprimirá una advertencia indicando que el sistema considerará todas las operaciones abiertas por el usuario.
Destructor
El destructor de la clase se encargará de liberar la memoria asignada al objeto CTrade cuando la instancia de CRiskManagement sea eliminada.
CRiskManagemet::~CRiskManagemet()
{
delete trade;
}
Métodos de Inicialización
Para facilitar la configuración y actualización de la gestión de riesgo, crearemos funciones específicas que permitan:
- Establecer el Stop Loss
- Configurar los parámetros de pérdidas y ganancias
1. Funciones para configurar los valores de las estructuras de pérdidas
Definiremos métodos que nos permitirán asignar valores a las variables clave para el cálculo de pérdidas y ganancias.
//--- Functions to assign values to variables for subsequent calculation of losses void SetPorcentages(double percentage_or_money_mdl, double percentage_or_money_mwl,double percentage_or_money_gmlpo, double percentage_or_money_ml, double percentage_or_money_mdp_); void SetEnums(ENUM_RISK_CALCULATION_MODE mode_mdl_, ENUM_RISK_CALCULATION_MODE mode_mwl_, ENUM_RISK_CALCULATION_MODE mode_gmlpo_, ENUM_RISK_CALCULATION_MODE mode_ml_, ENUM_RISK_CALCULATION_MODE mode_mdp_); void SetApplieds(ENUM_APPLIED_PERCENTAGES applied_mdl_, ENUM_APPLIED_PERCENTAGES applied_mwl_, ENUM_APPLIED_PERCENTAGES applied_gmlpo_, ENUM_APPLIED_PERCENTAGES applied_ml_, ENUM_APPLIED_PERCENTAGES applied_mdp_);
2. Asignación de porcentajes a las estructuras de pérdidas
El siguiente método asigna los valores de porcentaje o monto monetario a las estructuras correspondientes
void CRiskManagemet::SetPorcentages(double percentage_or_money_mdl,double percentage_or_money_mwl,double percentage_or_money_gmlpo,double percentage_or_money_ml,double percentage_or_money_mdp_) { this.gmlpo.assigned_percentage = percentage_or_money_gmlpo; this.mdl.assigned_percentage = percentage_or_money_mdl; this.ml.assigned_percentage = percentage_or_money_ml; this.mdp.assigned_percentage = percentage_or_money_mdp_; this.mwl.assigned_percentage = percentage_or_money_mwl; }
3. Inicialización de los modos de cálculo de riesgo
Este método establece el modo de cálculo de cada estructura. Si el usuario elige el modo basado en dinero (money), se asigna directamente el valor correspondiente.
void CRiskManagemet::SetEnums(ENUM_RISK_CALCULATION_MODE mode_mdl_,ENUM_RISK_CALCULATION_MODE mode_mwl_,ENUM_RISK_CALCULATION_MODE mode_gmlpo_,ENUM_RISK_CALCULATION_MODE mode_ml_,ENUM_RISK_CALCULATION_MODE mode_mdp_) { this.gmlpo.mode_calculation_risk = mode_gmlpo_; this.mdl.mode_calculation_risk = mode_mdl_; this.mdp.mode_calculation_risk = mode_mdp_; this.ml.mode_calculation_risk = mode_ml_; this.mwl.mode_calculation_risk = mode_mwl_; //-- If the money mode has been chosen, assign the variable that stores the money or percentage to the corresponding variables. this.gmlpo.value = this.gmlpo.mode_calculation_risk == money ? this.gmlpo.value : 0; this.mdp.value = this.mdp.mode_calculation_risk == money ? this.mdp.value : 0; this.mdl.value = this.mdl.mode_calculation_risk == money ? this.mdl.value : 0; this.ml.value = this.ml.mode_calculation_risk == money ? this.ml.value : 0; this.mwl.value = this.mwl.mode_calculation_risk == money ? this.mwl.value : 0; }
4. Asignación de los parámetros aplicados a la cuenta
Este método define cómo se aplicarán los porcentajes configurados a la cuenta.
void CRiskManagemet::SetApplieds(ENUM_APPLIED_PERCENTAGES applied_mdl_,ENUM_APPLIED_PERCENTAGES applied_mwl_,ENUM_APPLIED_PERCENTAGES applied_gmlpo_,ENUM_APPLIED_PERCENTAGES applied_ml_,ENUM_APPLIED_PERCENTAGES applied_mdp_) { this.gmlpo.percentage_applied_to = applied_gmlpo_; this.mdl.percentage_applied_to = applied_mdl_; this.mdp.percentage_applied_to = applied_mdp_; this.mwl.percentage_applied_to = applied_mwl_; this.ml.percentage_applied_to = applied_ml_; }
5. Funciones para establecer el stop loss:
Para establecer el stop loss usaremos 2 métodos simples para asignar la variable StopLoss:
- Metodo 1: el usuario define directamente los puntos del stop loss.
- Metodo 2: el usuario define la distancia entre el stop loss y el punto de entrada, y luego este se convierta en puntos (usando la función que codificamos en la 1.ª parte del artículo).
//--- Function to set the "StopLoss" variable, in points or distance inline void SetStopLoss(double dist_open_sl) { this.StopLoss = DistanceToPoint(dist_open_sl); } inline void SetStopLoss(long _sl_point_) { this.StopLoss = _sl_point_; }
Métodos para asignación de valores a pérdidas y ganancias
En esta sección, desarrollaremos los métodos necesarios para asignar valores a las pérdidas y ganancias dentro de nuestro sistema de gestión de riesgo.
1. Función general para la asignación de valores
Para garantizar una gestión de riesgos flexible y adaptable, crearemos una función general que calculará el valor correspondiente en función de un porcentaje aplicado a diferentes métricas de la cuenta.
Definición de la función:
La función principal que utilizaremos es la siguiente:
//--- General function to assign values to loss variables double GetValorWithApplied(const ENUM_APPLIED_PERCENTAGES applied,const double percentage_);
Esta función tomará dos parámetros clave:
- applied: Define a qué métrica de la cuenta se aplicará el porcentaje (saldo, margen libre, equity, etc.).
- percentage_: El porcentaje que se aplicará a la métrica seleccionada.
double CRiskManagemet::GetValorWithApplied(const ENUM_APPLIED_PERCENTAGES applied,const double percentage_) { if(this.mode_risk_managemet == propfirm_ftmo && percentage_ != this.mdp.assigned_percentage && percentage_ != this.gmlpo.assigned_percentage) return this.account_balance_propfirm * (percentage_/100.0); switch(applied) { case Balance: return NormalizeDouble((percentage_/100.0) * AccountInfoDouble(ACCOUNT_BALANCE),2); case ganancianeta: { if(this.account_profit <= 0) { PrintFormat("The total profit of the account which is %+.2f is invalid or negative",this.account_profit); return 0; } else return NormalizeDouble((percentage_/100.0) * this.account_profit,2); } case free_margin: { if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0) { PrintFormat("free margin of %+.2f is invalid",AccountInfoDouble(ACCOUNT_MARGIN_FREE)); return 0; } else return NormalizeDouble((percentage_/100.0) * AccountInfoDouble(ACCOUNT_MARGIN_FREE),2); } case equity: return NormalizeDouble((percentage_/100.0) * AccountInfoDouble(ACCOUNT_EQUITY),2); default: Print("Critical Error | It was not found that: ", EnumToString(applied), " be part of the allowed enumeration"); } return 0; }Explicación del código
- Gestión de cuentas de prop firms:
- En el caso de que la cuenta sea de una prop firm como FTMO, y el porcentaje aplicado no sea el correspondiente a la pérdida máxima diaria (mdp.assigned_percentage) o a la pérdida máxima por operación (gmlpo.assigned_percentage), el cálculo se realiza sobre el saldo de la cuenta de la prop firm.
- Cálculo sobre diferentes métricas de la cuenta:
- Balance: Aplica el porcentaje sobre el saldo total de la cuenta.
- ganancianeta: Aplica el porcentaje sobre la ganancia neta de la cuenta. Si las ganancias son negativas o nulas, se muestra un mensaje de error.
- free_margin: Aplica el porcentaje sobre el margen libre. Si el margen libre es negativo, la función devuelve 0.
- equity: Aplica el porcentaje sobre el equity actual de la cuenta.
- Manejo de errores:
- Si se pasa una métrica no válida en applied, la función imprimirá un mensaje de error y retornará 0.
2. Creación de Funciones para configurar variables de pérdidas y ganancias
Para gestionar eficientemente las pérdidas y ganancias, crearemos un conjunto de funciones que nos permitirán asignar valores de manera estructurada. En total, definiremos seis funciones, cinco de ellas destinadas a la configuración de las pérdidas principales, y una para calcular el lote ideal en función del Stop Loss.
1. Funciones de asignación de valores
El método de asignación de pérdidas y ganancias se basa en la siguiente lógica:
- Si el modo de cálculo es "money", se mantiene el valor previamente asignado.
- Si el cálculo no está en modo "money", se usa la función GetValorWithApplied para determinar el valor con base en el porcentaje asignado y la aplicación correspondiente.
- Si el porcentaje asignado es 0, se considera que la pérdida no será utilizada y, por lo tanto, se asigna un valor de 0.
2. Funciones de asignación
Las siguientes funciones implementan la lógica descrita para cada una de las pérdidas:
//--- Functions to assign values to internal variables void SetMDL() {this.mdl.value = this.mdl.mode_calculation_risk == money ? this.mdl.value : (this.mdl.assigned_percentage > 0 ? GetValorWithApplied(this.mdl.percentage_applied_to,mdl.assigned_percentage) : 0); } void SetMWL() {this.mwl.value = this.mwl.mode_calculation_risk == money ? this.mwl.value : (this.mwl.assigned_percentage > 0 ? GetValorWithApplied(this.mwl.percentage_applied_to,mwl.assigned_percentage) : 0); } void SetML() {this.ml.value = this.ml.mode_calculation_risk == money ? this.ml.value : (this.ml.assigned_percentage > 0 ? GetValorWithApplied(this.ml.percentage_applied_to,ml.assigned_percentage): 0); } void SetGMLPO() {this.gmlpo.value = this.gmlpo.mode_calculation_risk == money ? this.gmlpo.value : (this.gmlpo.assigned_percentage > 0 ? GetValorWithApplied(this.gmlpo.percentage_applied_to,gmlpo.assigned_percentage) : 0); } void SetMDP() {this.mdp.value = this.mdp.mode_calculation_risk == money ? this.mdp.value : (this.mdp.assigned_percentage > 0 ? GetValorWithApplied(this.mdp.percentage_applied_to,mdp.assigned_percentage) : 0); } void SetNMPLO(double& TLB_new, double tlb) { GetIdealLot(TLB_new,tlb,this.gmlpo.value,this.nmlpo,this.StopLoss); }
Cálculo del lote y stop loss basado en el riesgo por operación
En esta sección, implementaremos dos funciones esenciales para calcular de manera precisa el tamaño del lote y el stop loss, garantizando que cada operación se alinee con el nivel de riesgo que estamos dispuestos a asumir.
1. Función para calcular el lotaje
Para calcular el lote, utilizaremos las funciones que hemos desarrollado en el primer artículo, asegurándonos de que el lote se ajuste a nuestras reglas de riesgo predefinidas.
La función que nos permitirá obtener el lote adecuado es:
//--- Get the lot double GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot);Parámetros de la función:
- order_type:Especifica el tipo de orden (compra, venta, stops, limits, stop-limits, etc.).
- mode_get_lot: Indica el método de cálculo del lote (el cual explicamos en la sección de enumeraciones).
Objetivo de la Función
Esta función nos ayudará a calcular el tamaño ideal del lote basándonos en dos enfoques diferentes:
- En función del stop loss y el riesgo máximo por operación.
- En función del riesgo máximo por operación directamente.
Ahora, veamos cómo está implementado el cálculo dentro del código.
//+-----------------------------------------------------------------------------------------------+ //| Function to obtain the ideal lot based on the maximum loss per operation and the stop loss | //+-----------------------------------------------------------------------------------------------+ double CRiskManagemet::GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot) { if(mode_get_lot == GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION) { double MaxLote = GetMaxLote(order_type); SetNMPLO(this.lote,MaxLote); PrintFormat("Maximum loss in case the next operation fails %.2f ", this.nmlpo); } else { this.lote = GetLotByRiskPerOperation(this.gmlpo.value,order_type); } return this.lote; }
Modo 1: Cálculo del lote basándonos en el stop loss y el riesgo máximo por operación:
Cuando el usuario selecciona GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION, el sistema calcula el lote basándonos en el riesgo por operación y luego lo ajusta al stop loss, para que, en caso la operación resulte perdedora, se pierda lo que se arriesgó.
- Se obtiene el lote máximo permitido a través de la función GetMaxLote(order_type).
- Se ajusta el tamaño del lote mediante SetNMPLO(this.lote, MaxLote), evitando que se excedan los límites de la cuenta.
- Se imprime un mensaje informativo con la pérdida máxima esperada si la operación llega a stop loss.
Modo 2: Cálculo del lote basándonos en el riesgo máximo por operación:
Cuando el usuario selecciona otro modo de cálculo (por ejemplo, GET_LOT_BY_RISK_PER_OPERATION), el sistema ajusta el lote de manera más directa:
- La función GetLotByRiskPerOperation(this.gmlpo.value, order_type) determina el tamaño del lote basándonos en el riesgo máximo que estamos dispuestos a asumir en la operación.
- Este método es más simple y puede ser útil para traders que no dependen de un stop loss fijo, sino que ajustan su riesgo de manera más dinámica.
2. Función para calcular el stop loss
Como ya hemos desarrollado funciones auxiliares en secciones anteriores, esta función será relativamente sencilla, ya que reutilizaremos dichas funciones para calcular el stop loss de manera eficiente.
La función que nos ayudará a obtener el stop loss ideal es:
long GetSL(const ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50);Parámetros de la función:
- type – Especifica el tipo de orden: Puede ser una orden de compra, venta, stop, limit, stop-limit, etc.
- DEVIATION (opcional): Representa la desviación permitida en la ejecución de la orden, con un valor predeterminado de 100 puntos.
- STOP_LIMIT (opcional): Representa la distancia en puntos para órdenes del tipo STOP_LIMIT.
Estos parámetros garantizan que el cálculo del stop loss sea dinámico y adaptable a diferentes condiciones de mercado.
Implementación de la función:
//+----------------------------------------------------------------------------------+ //| Get the ideal stop loss based on a specified lot and the maximum loss per trade | //+----------------------------------------------------------------------------------+ long CRiskManagemet::GetSL(const ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50) { double lot; return CalculateSL(type,this.gmlpo.value,lot,DEVIATION,STOP_LIMIT); }
- ¿Cómo funciona esta función?
- Define la variable lot, que almacenará el tamaño del lote como resultado del cálculo del stop loss.
- Llama a la función CalculateSL(), pasando como parámetros:
- El tipo de orden (type), para asegurarnos de que el stop loss se calcule correctamente dependiendo de si es una compra o venta.
- El riesgo máximo por operación (this.gmlpo.value), lo que nos ayuda a establecer un stop loss que respete nuestra gestión de riesgo.
- La variable lot, que se actualizará basándonos en el tamaño del lote en uso.
- La desviación permitida (DEVIATION), que nos permite cierta flexibilidad en la ejecución de la orden.
- STOP_LIMIT: Representa la distancia en puntos para órdenes del tipo STOP_LIMIT.
- Finalmente, retornamos el stop loss en puntos
Funciones para obtener el valor de pérdidas y ganancias
Esta sección describe las funciones encargadas de obtener las métricas clave de pérdidas y ganancias dentro del sistema de trading. Estas funciones están declaradas como inline y const para garantizar eficiencia y evitar modificaciones no deseadas en las variables de la clase.
1. Funciones para obtener las máximas pérdidas y ganancias
Las siguientes funciones devuelven el valor almacenado en variables que registran la máxima ganancia y pérdida alcanzadas en distintos períodos:
inline double GetML() const { return this.ml.value; } inline double GetMWL() const { return this.mwl.value; } inline double GetMDL() const { return this.mdl.value; } inline double GetGMLPO() const { return this.gmlpo.value; } inline double GetNMLPO() const { return this.nmlpo; } inline double GetMDP() const { return this.mdp.value; }
Cada función proporciona un dato relevante sobre el comportamiento de las operaciones en distintos niveles de análisis.
2. Funciones para obtener el profit diario, semanal y total
Estas funciones devuelven las ganancias en diferentes períodos de tiempo, permitiendo evaluar el rendimiento del sistema.
//--- Obtain only profits: inline double GetGrossProfit() const { return this.gross_profit; } inline double GetWeeklyProfit() const { return this.weekly_profit; } inline double GetDailyProfit() const { return this.daily_profit; }
3. Función para cerrar operaciones abiertas según número mágico y banderas
Esta función tiene como objetivo cerrar todas las posiciones abiertas que cumplan con ciertos criterios.
Se basa en un sistema de banderas (flags) que permite especificar si se deben cerrar:
- Solo operaciones con ganancias.
- Solo operaciones con pérdidas.
- Ambas (todas las operaciones).
void CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT);
- Flags: Es un entero que representa las condiciones bajo las cuales se cerrarán las posiciones. Se pueden combinar diferentes banderas usando operadores bit a bit (|).
#define FLAG_CLOSE_ALL_PROFIT 2 //Flag indicating to close only operations with profit #define FLAG_CLOSE_ALL_LOSS 4 //Flag indicating to close only operations without profit
Aquí las banderas están como potencias de 2.
Cada bandera representa un valor que puede activarse de forma independiente o combinada:
Bandera | Valor (Decimal) | Valor (Binario) | Significado |
---|---|---|---|
FLAG_CLOSE_ALL_PROFIT | 2 | 00000010 | Cierra solo operaciones con ganancias. |
FLAG_CLOSE_ALL_LOSS | 4 | 00000100 | Cierra solo operaciones en pérdida. |
2. Función para cerrar posiciones
Esta función recorre todas las posiciones abiertas y las cierra según las banderas activadas.
3. Recorrer todas las posiciones abiertas
for(int i = PositionsTotal() - 1; i >= 0; i--)
- PositionsTotal() devuelve el número total de posiciones abiertas.
- El bucle recorre las posiciones en orden inverso (de la última a la primera) para evitar errores al cerrar posiciones en tiempo real.
4. Obtener el identificador (ticket) de la posición
ulong position_ticket = PositionGetTicket(i);
- Cada posición tiene un ticket único que la identifica.
5. Seleccionar la posición
if (!PositionSelectByTicket(position_ticket)) continue;
- PositionSelectByTicket(position_ticket) intenta seleccionar la posición con el ticket obtenido.
- Si la selección falla, se pasa a la siguiente posición con continue.
6. Verificar el número mágico
ulong magic = PositionGetInteger(POSITION_MAGIC); if (magic != this.magic_number && this.magic_number != NOT_MAGIC_NUMBER) continue;
- Se obtiene el número mágico de la posición.
- Si la posición tiene un número mágico diferente al esperado y no se permite operar sobre cualquier orden (NOT_MAGIC_NUMBER), la ignoramos.
7. Obtener el profit de la posición
double profit = PositionGetDouble(POSITION_PROFIT);
- POSITION_PROFIT devuelve el beneficio actual de la operación. Puede ser positivo (ganancia) o negativo (pérdida).
8. Verificar las banderas y cerrar operaciones
if ((flags & FLAG_CLOSE_ALL_PROFIT) != 0 && profit > 0) { trade.PositionClose(position_ticket); } else if ((flags & FLAG_CLOSE_ALL_LOSS) != 0 && profit < 0) { trade.PositionClose(position_ticket); }
- Se usa & para comprobar si la bandera está activada.
- Si FLAG_CLOSE_ALL_PROFIT está activa y la operación tiene ganancias, se cierra.
- Si FLAG_CLOSE_ALL_LOSS está activa y la operación está en pérdidas, se cierra.
void CRiskManagemet::CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong position_ticket = PositionGetTicket(i); if(!PositionSelectByTicket(position_ticket)) continue; // If you don't select the position, continue double profit = PositionGetDouble(POSITION_PROFIT); // Check flags before closing the position if((flags & FLAG_CLOSE_ALL_PROFIT) != 0 && profit > 0) // Close only profit positions { trade.PositionClose(position_ticket); } else if((flags & FLAG_CLOSE_ALL_LOSS) != 0 && profit < 0) // Close only losing positions { trade.PositionClose(position_ticket); } } }
Eventos programados para nuevo día y nueva semana
Para finalizar este artículo, programaremos las últimas funciones que se ejecutarán en eventos específicos. En particular, crearemos dos funciones clave:
- Evento Diario: Se ejecutará al comienzo de cada nuevo día.
- Evento Semanal: Se ejecutará al inicio de cada nueva semana.
Estos eventos ayudarán a gestionar las pérdidas y ganancias de manera estructurada, asegurando que los valores se actualicen correctamente.
Evento Diario
Cada día, es necesario recalcular y establecer los valores de todas las pérdidas y ganancias máximas, además de reiniciar el profit diario acumulado a cero. También imprimiremos los valores de las pérdidas y ganancias en el log para su monitoreo.//+------------------------------------------------------------------+ //| Function that runs every new day | //+------------------------------------------------------------------+ void CRiskManagemet::OnNewDay(void) { SetMWL(); SetMDL(); SetML(); SetMDP(); SetGMLPO(); this.daily_profit = 0; this.last_day_time = iTime(_Symbol,PERIOD_D1,0); Print(" New day "); Print(StringFormat("%-6s| %s", "Losses", "Loss")); Print(StringFormat("%-6s| %.2f", "MDP", this.mdp.value)); Print(StringFormat("%-6s| %.2f", "MWL", this.mwl.value)); Print(StringFormat("%-6s| %.2f", "ML", this.ml.value)); Print(StringFormat("%-6s| %.2f", "MDl", this.mdl.value)); Print(StringFormat("%-6s| %.2f", "GMLPO", this.gmlpo.value)); }
- Se llaman todas las funciones necesarias para establecer los valores de pérdidas y ganancias.
- Se reinicia la variable daily_profit para comenzar con un nuevo día sin arrastrar ganancias o pérdidas previas.
- Se almacena el timestamp del último día registrado con iTime(_Symbol, PERIOD_D1, 0).
- Se imprimen los valores de pérdidas en el log para hacer un seguimiento del sistema.
Evento Semanal
El evento semanal se encargará de registrar el inicio de una nueva semana y restablecer a cero el profit acumulado de la semana anterior.//+------------------------------------------------------------------+ //| Function that runs every new week | //+------------------------------------------------------------------+ void CRiskManagemet::OnNewWeek(void) { this.last_weekly_time = iTime(_Symbol,PERIOD_W1,0); this.weekly_profit = 0; }
- Se actualiza la variable last_weekly_time con el tiempo de apertura de la nueva semana.
- Se reinicia la variable weekly_profit a cero para no acumular las ganancias o pérdidas de la semana anterior.
Detección de nuevos eventos (Día/Semana/Período Personalizado)
Para ejecutar estas funciones automáticamente dentro de un Expert Advisor (EA), podemos verificar si ha cambiado el período utilizando una variable datetime. Esto nos permite detectar un nuevo día, semana o cualquier otro marco de tiempo (H1, H12, etc.).
Ejemplo de detección de nuevo día:
datetime prev_time = 0; void OnTick() { if(prev_time != iTime(_Symbol, PERIOD_D1, 0)) { Print("New day detected"); prev_time = iTime(_Symbol, PERIOD_D1, 0); OnNewDay(); // Call the corresponding function } }
Ejemplo de detección de nueva semana:
datetime prev_week_time = 0; void OnTick() { if(prev_week_time != iTime(_Symbol, PERIOD_W1, 0)) { Print("New week detected") //Call the corresponding function prev_week_time = iTime(_Symbol, PERIOD_W1, 0); } }
Explicación:
- Se define una variable datetime para almacenar el tiempo anterior registrado.
- En cada tick (OnTick), se compara el tiempo actual con el registrado en prev_time o prev_week_time.
- Si han cambiado, significa que ha iniciado un nuevo día o semana, por lo que se llama a la función OnNewDay() u OnNewWeek().
- Se actualiza la variable prev_time o prev_week_time con el nuevo valor.
Conclusión
En este artículo, hemos desarrollado la primera parte de la clase de gestión de riesgo CRiskManagement. En esta etapa inicial, nuestro enfoque principal ha sido asignar valores a las pérdidas y ganancias máximas, estableciendo así la base para un control estructurado del riesgo.
Aunque la clase aún no está lista para ser utilizada en un entorno operativo, ya podemos observar cómo se asignan y gestionan estos valores dentro del sistema.
En el próximo artículo, completaremos la implementación de esta clase agregando funciones clave que permitirán:
- Verificar si las pérdidas máximas han sido superadas y ejecutar acciones correspondientes.
- Incorporar nuevos eventos para mejorar la gestión del riesgo.
- Crear funciones específicas, como la actualización automática de la pérdida máxima diaria en cuentas de tipo Prop Firm, como FTMO.
Con estas mejoras, la clase CRiskManagement estará lista para gestionar de manera eficiente los límites de riesgo y adaptarse a diferentes condiciones del mercado.
Archivos usados/mejorados en este artículo:
Nombre del archivo | Tipo | Descripción |
---|---|---|
Risk_Management.mqh | .mqh (archivo incluido) | Archivo principal que contiene funciones generales y la implementación de la clase CRiskManagement, responsable de gestionar el riesgo dentro del sistema. En este archivo se definen, desarrollan y amplían todas las funcionalidades relacionadas con la gestión de pérdidas y ganancias. |





- 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