preview
Gestión de Riesgo (Parte 1): Fundamentos para Construir una Clase de Gestión de Riesgo

Gestión de Riesgo (Parte 1): Fundamentos para Construir una Clase de Gestión de Riesgo

MetaTrader 5Ejemplos | 21 enero 2025, 15:35
533 0
Niquel Mendoza
Niquel Mendoza



Introducción 

En este artículo exploraremos qué es la gestión de riesgo en el trading y su importancia en la operativa automatizada. Comenzaremos desde los conceptos básicos, sentando las bases para entender cómo una correcta gestión de riesgo puede 

Marcar la diferencia entre el éxito y el fracaso en los mercados financieros. Posteriormente, construiremos paso a paso una clase en MQL5 que implemente una gestión de riesgo completa, permitiendo controlar aspectos clave como el tamaño del lote, pérdidas máximas, y ganancias esperadas.


¿Qué es la gestión de riesgo? 

La gestión de riesgo es un pilar fundamental en cualquier estrategia de trading. Su propósito principal es supervisar y controlar las posiciones abiertas, asegurando que las pérdidas no superen los límites establecidos por el trader, como pérdidas diarias, semanales o totales.

Además, la gestión de riesgo permite determinar el tamaño de lote adecuado para cada operación, basándose en las reglas y preferencias del usuario. Esto no solo protege el capital, sino que también optimiza el rendimiento de la estrategia, garantizando que las operaciones se ajusten al perfil de riesgo definido.

En resumen, una buena gestión de riesgo no solo reduce las posibilidades de pérdidas catastróficas, sino que también proporciona un marco disciplinado para tomar decisiones financieras inteligentes.


Importancia en el trading automatizado  

La gestión de riesgo desempeña un papel crucial en el trading automatizado, ya que funciona como un sistema de control que evita errores costosos, como la sobre operación o la exposición a riesgos innecesarios. En el contexto de bots de trading, donde las decisiones se toman de forma completamente automatizada, una adecuada gestión de riesgo asegura que las estrategias se ejecuten de manera disciplinada y eficiente.

Esto es especialmente valioso en escenarios como las pruebas de fondeo, donde cumplir con estrictos límites de pérdida diaria, semanal o total puede ser la diferencia entre aprobar o fallar. La gestión de riesgo permite establecer estas barreras de manera precisa, protegiendo el capital del usuario y optimizando su desempeño en entornos competitivos.

Además, ayuda al bot a operar con un enfoque más estratégico, estableciendo límites claros para evitar operar en exceso o asumir riesgos desproporcionados. Al calcular automáticamente el tamaño de los lotes y limitar las pérdidas por operación, la gestión de riesgo no solo protege el capital, sino que también proporciona tranquilidad al trader al saber que su bot está trabajando dentro de parámetros controlados y seguros.


Conceptos Previos de una Gestión de riesgo 

Antes de comenzar a programar, es esencial entender las principales variables y conceptos que manejaremos en la gestión de riesgo. Estos conceptos son fundamentales para diseñar un sistema efectivo que proteja el capital del usuario y permita operar de manera controlada. A continuación, desglosamos cada uno de ellos:

1. Pérdida Máxima Diaria

Es el límite máximo de pérdida que un bot puede acumular en un día (24 horas). Si este límite es alcanzado, lo más común es que el bot cierre todas las operaciones abiertas y suspenda cualquier actividad de trading hasta el día siguiente. Este concepto ayuda a evitar que una racha de malas operaciones impacte severamente el capital.

2. Pérdida Máxima Semanal

Similar a la pérdida diaria, pero aplicada a un horizonte de una semana. Si el bot supera este límite, dejará de operar hasta el inicio de la próxima semana. Este parámetro es ideal para prevenir pérdidas significativas en periodos más largos.

3. Pérdida Máxima Total

Es el límite absoluto de pérdida que, si se alcanza, activa una estrategia especial de recuperación. Esto puede incluir reducir el tamaño de los lotes y operar con más cautela para intentar recuperar el capital perdido de manera gradual. Este concepto permite controlar el riesgo global de la cuenta.

4. Pérdida Máxima por Operación

Define la máxima pérdida que una única operación puede tener. Este valor es crucial, ya que permite calcular automáticamente el lote ideal para cada operación en función del riesgo que el usuario esté dispuesto a asumir.

5. Ganancias Diarias, Semanales y Totales

Son variables que registran las ganancias acumuladas en diferentes periodos de tiempo. Estas métricas son útiles para evaluar el desempeño del bot y ajustar las estrategias según los resultados obtenidos.


Creamos el archivo de inclusión y explicamos el plan 

En esta sección ya empezaremos a programar el archivo de inclusión.

1. Nos dirigimos en la parte superior de nuestra plataforma metatrader, y hacemos clic en el texto "IDE":

 IDE-1

2. En la esquina superior izquierda del meta editor haz clic en la pestaña archivo, haz clic en nuevo, te tiene que aparecer algo como:

IDE-2

 3. Selecciona "include"  y haz clic en siguiente:

 IDE-3

4. Personaliza el include poniéndole un nombre y autor:

IDE-4

Con esto ya terminaos la creación del archivo, esto es solo el comienzo, ahora detallaré el plan de como funcionara esta gestión de riesgo.

A continuación presento un esquema de cómo será el funcionamiento de la gestión de riesgo:

Map-1

Sección
Descripción   Frecuencia de Ejecución
1. Set Calculation   Variables - En esta fase inicial (que se realiza una sola vez), se establecen todas las variables necesarias para el cálculo de   pérdidas y profits.
- Las principales tareas incluyen:
- Definir el número mágico para identificar operaciones específicas del EA.
- Configurar el balance inicial, especialmente relevante si se opera en cuentas de fondeo o prop firms.
- Declarar los porcentajes aplicables al riesgo, seleccionando si las pérdidas se calcularán en dinero o                       como porcentaje del balance/capital.
- Si se elige calcular por porcentaje, el usuario debe especificar el valor base para aplicar dicho porcentaje (por     ejemplo, balance total, equito, profit total o margen libre).
 Solo se ejecuta una vez o al configurar   el EA.
2. Cálculo de Pérdidas y   Profits
- En esta fase, se calcula el estado actual de pérdidas y ganancias de la cuenta. Esto incluye:
- Calcular las pérdidas totales acumuladas.
- Registrar los profits diarios, semanales, o por operación.
- Comparar las pérdidas acumuladas con los límites establecidos en la sección anterior.
- Este proceso se realiza periódicamente según las necesidades del usuario.
 Ejecución diaria, al abrir una   operación,   o semanalmente según lo   configurado.
3. Verificación en       Tiempo Real - Durante el flujo en tiempo real, el EA realiza una verificación constante (por cada tick) para asegurarse de que     las pérdidas actuales no hayan excedido los límites definidos.
- Si alguna de las variables de pérdida es superada, el EA ejecutará un cierre inmediato de todas las operaciones   abiertas para evitar mayores pérdidas.
 En cada tick (proceso en tiempo real).

Con esto en mente pasamos a la creación de las primeras funciones.


Creación de funciones para el cálculo del lotaje 

Antes de comenzar a desarrollar la clase, es necesario crear primero las funciones que nos permitirán calcular el lote.

Cálculo del Lote Ideal

Para determinar el lote ideal, primero necesitamos calcular el lote bruto, que es el máximo lote que nuestra cuenta puede comprar o vender. Este cálculo se basa en conocer el margen necesario en la divisa de la cuenta para operar un lote. Una vez que obtenemos este valor, lo dividimos entre el margen libre de la cuenta, redondeamos el resultado, y así obtenemos el lote máximo permitido por nuestra cuenta.

Requisitos Previos

Antes de proceder con el cálculo, necesitamos determinar el margen requerido por un lote para cualquier símbolo. En este ejemplo, se utilizará el oro como símbolo, pero el proceso es aplicable a cualquier otro símbolo financiero.

El objetivo principal es tener una base sólida para calcular lotes de manera eficiente y adaptada al balance y margen disponible en nuestra cuenta de trading.

 MARGIN-1

Como se puede observar, el margen inicial aproximado para comprar un lote de oro es de 1,326 USD. Por lo tanto, para calcular el lote máximo permitido, simplemente dividimos el margen libre disponible en la cuenta entre el margen inicial requerido. Esta relación puede representarse de la siguiente manera:

MARGIN-2

Free Margin:

  • Es el capital disponible en tu cuenta que puedes usar para abrir nuevas operaciones. En MetaTrader, se calcula como:

MARGIN-3

Calcular el precio para cualquier tipo de orden

Ahora que sabemos cómo calcular el tamaño máximo de lote, el siguiente paso es programarlo. Sin embargo, antes de implementar esta funcionalidad, necesitamos determinar el precio al cual se ejecutará la orden. Para esto, crearemos una función llamada PriceByOrderType que calculará y devolverá el precio correspondiente en función del tipo de orden.

double PriceByOrderType(const string symbol, const ENUM_ORDER_TYPE order_type, double DEVIATION = 100, double STOP_LIMIT = 50)

Parámetros:

  1. symbol: El símbolo de trading (por ejemplo, "EURUSD") en el cual se ejecutará la orden.
  2. order_type: El tipo de orden, basado en la enumeración ENUM_ORDER_TYPE.
  3. DEVIATION: La desviación permitida en puntos.
  4. STOP_LIMIT: La distancia en puntos para órdenes del tipo STOP_LIMIT.

Paso 1: Crear las variables necesarias

Primero, declaramos las variables que almacenarán los dígitos del símbolo, el valor del punto y los precios actuales (bid y ask) en una estructura MqlTick.

int     digits=0; 
double  point=0; 
MqlTick tick={}; 

Paso 2: Asignar valores a las variables

Usamos funciones para obtener la información del símbolo, como los dígitos, el valor del punto y los precios actuales.

Obtener el valor de SYMBOL_POINT:
ResetLastError(); 
if(!SymbolInfoDouble(symbol, SYMBOL_POINT, point)) 
  { 
   Print("SymbolInfoDouble() failed. Error ", GetLastError()); 
   return 0; 
  } 

Obtener el valor de SYMBOL_DIGITS:

long value=0; 
if(!SymbolInfoInteger(symbol, SYMBOL_DIGITS, value)) 
  { 
   Print("SymbolInfoInteger() failed. Error ", GetLastError()); 
   return 0; 
  } 
digits=(int)value; 

Obtener los precios actuales del símbolo:

if(!SymbolInfoTick(symbol, tick)) 
  { 
   Print("SymbolInfoTick() failed. Error ", GetLastError()); 
   return 0; 
  } 

Paso 3: Calcular el precio basado en el tipo de orden

Dependiendo del tipo de orden, devolvemos el precio correspondiente usando un switch.

switch(order_type) 
  { 
   case ORDER_TYPE_BUY              :  return(tick.ask); 
   case ORDER_TYPE_SELL             :  return(tick.bid); 
   case ORDER_TYPE_BUY_LIMIT        :  return(NormalizeDouble(tick.ask - DEVIATION * point, digits)); 
   case ORDER_TYPE_SELL_LIMIT       :  return(NormalizeDouble(tick.bid + DEVIATION * point, digits)); 
   case ORDER_TYPE_BUY_STOP         :  return(NormalizeDouble(tick.ask + DEVIATION * point, digits)); 
   case ORDER_TYPE_SELL_STOP        :  return(NormalizeDouble(tick.bid - DEVIATION * point, digits)); 
   case ORDER_TYPE_BUY_STOP_LIMIT   :  return(NormalizeDouble(tick.ask + DEVIATION * point - STOP_LIMIT * point, digits)); 
   case ORDER_TYPE_SELL_STOP_LIMIT  :  return(NormalizeDouble(tick.bid - DEVIATION * point + STOP_LIMIT * point, digits)); 
   default                          :  return(0); 
  } 

Aquí está la implementación final de la función:

double PriceByOrderType(const string symbol, const ENUM_ORDER_TYPE order_type, double DEVIATION = 100, double STOP_LIMIT = 50) 
  {
   int     digits=0; 
   double  point=0; 
   MqlTick tick={}; 

//--- we get the Point value of the symbol
   ResetLastError(); 
   if(!SymbolInfoDouble(symbol, SYMBOL_POINT, point)) 
     { 
      Print("SymbolInfoDouble() failed. Error ", GetLastError()); 
      return 0; 
     } 

//--- we get the Digits value of the symbol
   long value=0; 
   if(!SymbolInfoInteger(symbol, SYMBOL_DIGITS, value)) 
     { 
      Print("SymbolInfoInteger() failed. Error ", GetLastError()); 
      return 0; 
     } 
   digits=(int)value; 

//--- we get the latest prices of the symbol
   if(!SymbolInfoTick(symbol, tick)) 
     { 
      Print("SymbolInfoTick() failed. Error ", GetLastError()); 
      return 0; 
     } 

//--- Depending on the type of order, we return the price
   switch(order_type) 
     { 
      case ORDER_TYPE_BUY              :  return(tick.ask); 
      case ORDER_TYPE_SELL             :  return(tick.bid); 
      case ORDER_TYPE_BUY_LIMIT        :  return(NormalizeDouble(tick.ask - DEVIATION * point, digits)); 
      case ORDER_TYPE_SELL_LIMIT       :  return(NormalizeDouble(tick.bid + DEVIATION * point, digits)); 
      case ORDER_TYPE_BUY_STOP         :  return(NormalizeDouble(tick.ask + DEVIATION * point, digits)); 
      case ORDER_TYPE_SELL_STOP        :  return(NormalizeDouble(tick.bid - DEVIATION * point, digits)); 
      case ORDER_TYPE_BUY_STOP_LIMIT   :  return(NormalizeDouble(tick.ask + DEVIATION * point - STOP_LIMIT * point, digits)); 
      case ORDER_TYPE_SELL_STOP_LIMIT  :  return(NormalizeDouble(tick.bid - DEVIATION * point + STOP_LIMIT * point, digits)); 
      default                          :  return(0); 
     } 
  } 

Además, hará falta una función para obtener el tipo de orden ha mercado por el tipo de orden, esto sencillamente:

ENUM_ORDER_TYPE MarketOrderByOrderType(ENUM_ORDER_TYPE type) 
  { 
   switch(type) 
     { 
      case ORDER_TYPE_BUY  : case ORDER_TYPE_BUY_LIMIT  : case ORDER_TYPE_BUY_STOP  : case ORDER_TYPE_BUY_STOP_LIMIT  : 
        return(ORDER_TYPE_BUY); 
      case ORDER_TYPE_SELL : case ORDER_TYPE_SELL_LIMIT : case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : 
        return(ORDER_TYPE_SELL); 
     } 
   return(WRONG_VALUE); 
  }

Cálculo del lote máximo:

La función GetMaxLote calcula el tamaño máximo de lote que puede abrirse según el margen libre disponible y el tipo de orden especificado. Es una herramienta clave para la gestión de riesgo y garantiza que las operaciones cumplan con los requisitos de margen del broker.

1. Crear los parámetros de la función

La función recibe los siguientes parámetros

double GetMaxLote(ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50)
  • Type: Define el tipo de orden, como ORDER_TYPE_BUY u ORDER_TYPE_SELL. Este parámetro es esencial para calcular correctamente el precio y el margen.
  • DEVIATION: Indica la desviación permitida en puntos para órdenes pendientes. Su valor predeterminado es 100.
  • STOP_LIMIT: Representa la distancia en puntos para órdenes del tipo STOP_LIMIT. Su valor predeterminado es 50.
  • 2. Inicializar las variables necesarias

    Se declaran cuatro variables del tipo double y una de la enumeración ORDER_TYPE que se usarán en los cálculos:

       //--- Set variables
       double VOLUME = 1.0; //Initial volume size
       ENUM_ORDER_TYPE new_type = MarketOrderByOrderType(type); 
       double price = PriceByOrderType(_Symbol, type, DEVIATION, STOP_LIMIT); // Price for the given order type
       double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); // Volume step for the symbol
       double margin = EMPTY_VALUE; // Required margin, initialized as empty

    3. Calcular el margen requerido para un lote

    Se utiliza la función OrderCalcMargin para determinar el margen necesario para abrir un lote con las condiciones actuales del mercado. En caso de fallo, se imprime un mensaje de error y se retorna 0:

    ResetLastError(); 
    if (!OrderCalcMargin(new_type, _Symbol, VOLUME, price, margin)) 
      { 
       Print("OrderCalcMargin() failed. Error ", GetLastError()); 
       return 0; // Exit the function if margin calculation fails
      } 
    

    4. Calcular el tamaño máximo de lote

    Se utiliza la fórmula mencionada para calcular el tamaño máximo de lote. Esto incluye dividir el margen libre por el margen requerido, normalizar el resultado al paso de volumen permitido, y redondear hacia abajo para evitar errores:

    double result = MathFloor((AccountInfoDouble(ACCOUNT_MARGIN_FREE) / margin) / volume_step) * volume_step; 
    

    5. Retornar el resultado

    Finalmente, se retorna el tamaño máximo de lote calculado:

    return result; // Return the maximum lot size
    

    Función completa:

    double GetMaxLote(ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50) 
      { 
       //--- Set variables
       double VOLUME = 1.0; // Initial volume size
       ENUM_ORDER_TYPE new_type = MarketOrderByOrderType(type); 
       double price = PriceByOrderType(_Symbol, type, DEVIATION, STOP_LIMIT); // Price for the given order type
       double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); // Volume step for the symbol
       double margin = EMPTY_VALUE; // Required margin, initialized as empty
      
       //--- Get margin for one lot
       ResetLastError(); 
       if (!OrderCalcMargin(new_type, _Symbol, VOLUME, price, margin)) 
         { 
          Print("OrderCalcMargin() failed. Error ", GetLastError()); 
          return 0; // Exit the function if margin calculation fails
         }
       //--- Calculate the maximum lot size
       double result = MathFloor((AccountInfoDouble(ACCOUNT_MARGIN_FREE) / margin) / volume_step) * volume_step; 
       return result; // Return the maximum lot size
      }


    Creación de funciones para obtener el profit 

    Una vez finalizadas las funciones para obtener el lote máximo, el siguiente paso es desarrollar las funciones que calculen el profit desde una fecha específica hasta el tiempo actual. Esto es crucial porque, al evaluar cada tick, necesitamos determinar si una variable de pérdida máxima ha sido superada. Para ello, dependemos de las variables que almacenan el profit. Por ejemplo, para verificar si se ha excedido la pérdida máxima diaria, es indispensable conocer el profit acumulado del día, además de la equidad actual.

    El cálculo del profit actual se realizará utilizando las funciones disponibles para trabajar con el historial de órdenes y transacciones. Esto nos permitirá obtener datos precisos y actualizados sobre las ganancias o pérdidas en un periodo determinado.

    Descripción detallada de la función

    1. Inicialización de variables y reinicio de errores:

    double total_net_profit = 0.0; // Initialize the total net profit
    ResetLastError(); // Reset any previous errors
    
  • total_net_profit: se inicializa en 0.0, lo que significa que aún no se ha calculado ningún beneficio neto.
  • ResetLastError: asegura que cualquier error previo que haya podido ocurrir en el código sea reseteado antes de comenzar.
  • 2. Verificación de la fecha de inicio (start_date):

    if((start_date > 0 || start_date != D'1971.01.01 00:00'))
    

    • Esta línea verifica si la fecha proporcionada como start_date es válida (es decir, no es una fecha predeterminada inválida como 1971.01.01, ni una fecha cero). Si la fecha es válida, el código continúa con la selección del historial de operaciones.

    3. Selección del historial de operaciones:

    if(!HistorySelect(start_date, TimeCurrent())) 
    {
       Print("Error when selecting orders: ", _LastError); 
       return 0.0; // Exit if unable to select the history
    }
    • HistorySelect: selecciona el historial de operaciones desde la fecha proporcionada (start_date) hasta el momento actual (TimeCurrent).
    • Si la selección del historial falla, se imprime un mensaje de error y la función termina retornando 0.

    4. Obtención del total de operaciones:

    int total_deals = HistoryDealsTotal(); // Get the total number of deals in history
    

    • HistoryDealsTotal: devuelve el número total de operaciones que se encuentran en el historial, lo que permite saber cuántas operaciones hay para iterar.

    5. Iteración a través de todas las operaciones:

    for(int i = 0; i < total_deals; i++)
    {
       ulong deal_ticket = HistoryDealGetTicket(i); // Retrieve the deal ticket
    • En este punto, se inicia un ciclo for que recorrerá todas las operaciones en el historial.
    • HistoryDealGetTicket: obtiene el ticket único de la operación en la posición i, lo cual es necesario para obtener detalles de la operación.

    6. Filtrar las operaciones de tipo "balance":

    if(HistoryDealGetInteger(deal_ticket, DEAL_TYPE) == DEAL_TYPE_BALANCE) continue;

    Si el tipo de la operación es balance (ajustes de saldo y no operaciones reales), se omite esa operación y el ciclo continúa con la siguiente.

    7. Obtener detalles sobre la operación:

    ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); // Get deal entry type
    long deal_close_time_long = HistoryDealGetInteger(deal_ticket, DEAL_TIME); // Get deal close time (as long)
    datetime deal_close_time = (datetime)deal_close_time_long; // Explicit conversion to datetime
    ulong position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); // Get the position ID
    • deal_entry: Define si la operación fue de entrada o salida (se usa para saber si la operación fue una apertura o cierre).
    • deal_close_time: Es el tiempo de cierre de la operación. Se convierte a tipo datetime para su fácil manejo.
    • position_id: Es el ID de la posición asociada con esta operación, lo que ayuda a verificar el magic number.

    8. Filtrar operaciones según fecha y tipo:

    if(deal_close_time >= start_date && (deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_IN))
    

    • La condición asegura que solo se consideren las operaciones cuyo tiempo de cierre sea mayor o igual a la fecha de inicio start_date y que sean operaciones de entrada o salida válidas.

    9. Filtrar operaciones por Magic Number y tipo de inclusión:

    if((HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == specific_magic || specific_magic == GetMagic(position_id)) 
       || include_all_magic == true)
    

  • HistoryDealGetInteger: obtiene el magic number de la operación.
  • Si el magic number de la operación coincide con el proporcionado (specific_magic), o si se permite incluir todas las operaciones (cuando include_all_magic es true), se calcula el beneficio neto de la operación.

  • 10. Cálculo del beneficio neto de la operación:

    double deal_profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); // Retrieve profit from the deal
    double deal_commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); // Retrieve commission
    double deal_swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP); // Retrieve swap fees
    
    double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit for the deal
    total_net_profit += deal_net_profit; // Add to the total net profit
    
  • deal_profit: Obtiene el beneficio de la operación.
  • deal_commission: Obtiene la comisión de la operación.
  • deal_swap: Obtiene el swap de la operación (interés o cargo por mantener la operación abierta por la noche).
  • El beneficio neto de la operación se calcula sumando estos tres valores, y se agrega al total_net_profit.

  • 11. Devolver el beneficio neto total:

    return NormalizeDouble(total_net_profit, 2); // Return the total net profit rounded to 2 decimals
    

    • Finalmente, se retorna el beneficio neto total redondeado a 2 decimales usando NormalizeDouble para asegurarse de que el valor esté en el formato adecuado para su uso.

    Función completa:

    double GetNetProfitSince(bool include_all_magic, ulong specific_magic, datetime start_date)
    {
       double total_net_profit = 0.0; // Initialize the total net profit
       ResetLastError(); // Reset any previous errors
    
       // Check if the start date is valid
       if((start_date > 0 || start_date != D'1971.01.01 00:00'))
       {   
          // Select the order history from the given start date to the current time
          if(!HistorySelect(start_date, TimeCurrent())) 
          {
             Print("Error when selecting orders: ", _LastError); 
             return 0.0; // Exit if unable to select the history
          }
    
          int total_deals = HistoryDealsTotal(); // Get the total number of deals in history
      
          // Iterate through all deals
          for(int i = 0; i < total_deals; i++)
          {
             ulong deal_ticket = HistoryDealGetTicket(i); // Retrieve the deal ticket
    
             // Skip balance-type deals
             if(HistoryDealGetInteger(deal_ticket, DEAL_TYPE) == DEAL_TYPE_BALANCE) continue;            
    
             ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); // Get deal entry type
             long deal_close_time_long = HistoryDealGetInteger(deal_ticket, DEAL_TIME); // Get deal close time (as long)
             datetime deal_close_time = (datetime)deal_close_time_long; // Explicit conversion to datetime
             ulong position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); // Get the position ID
    
             // Check if the deal is within the specified date range and is a valid entry/exit deal
             if(deal_close_time >= start_date && (deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_IN))
             {             
                // Check if the deal matches the specified magic number or if all deals are to be included
                if((HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == specific_magic || specific_magic == GetMagic(position_id)) 
                   || include_all_magic == true)
                {
                   double deal_profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); // Retrieve profit from the deal
                   double deal_commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); // Retrieve commission
                   double deal_swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP); // Retrieve swap fees
                   
                   double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit for the deal
                   total_net_profit += deal_net_profit; // Add to the total net profit
                }
             }
          }
       }
         
       return NormalizeDouble(total_net_profit, 2); // Return the total net profit rounded to 2 decimals
    }

    Función extra para poder el número mágico de una orden:

    ulong GetMagic(const ulong ticket)
    {
    HistoryOrderSelect(ticket);
    return HistoryOrderGetInteger(ticket,ORDER_MAGIC); 
    } 


    Lo ponemos todo a prueba creando un script simple con el archivo de inclusión 

    Ahora vamos a crear una función que convierta una distancia absoluta a unidades de puntos para el símbolo actual. Esta conversión es fundamental en trading, ya que los puntos son la medida estándar utilizada para calcular niveles de precios, stops, y objetivos.

    Fórmula matemática

    La fórmula para calcular la distancia en puntos es sencilla:

    EXTRA-1

    Donde:

    • dist es la distancia absoluta que queremos convertir.
    • pointSize es el tamaño de un punto del instrumento financiero (por ejemplo, 0.0001 para EUR/USD).

    Representando la fórmula en código

    Para implementar esta fórmula en MQL5, necesitamos los siguientes pasos:

    1. Obtener el tamaño del punto (pointSize)
      Utilizamos la función SymbolInfoDouble para obtener el valor del punto del símbolo actual. El parámetro _Symbol representa el símbolo en ejecución, y SYMBOL_POINT nos proporciona su tamaño en puntos.      

    double pointSize = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    
          2.  Dividir la distancia por el tamaño del punto y convertir a entero
               Dividimos la distancia (dist) entre el tamaño del punto (pointSize) para calcular la cantidad de puntos. Luego, convertimos el resultado a un valor entero usando int, ya que los puntos son siempre números enteros.
    return (int)(dist / pointSize);
    

    Función completa

    A continuación, presentamos la función en su forma final:

    int DistanceToPoint(double dist)
    {
      double pointSize = SymbolInfoDouble(_Symbol, SYMBOL_POINT); // Get the point size for the current symbol
      return (int)(dist / pointSize); // Calculate and return the distance in points 
    }

    Para poner en práctica lo escrito en este artículo crearemos dos scripts.

    Ahora crearemos 2 funciones importantes, una para poder obtener el lote ideal basándonos en la pérdida por operación y otra para obtener el stop loss ideal en puntos del símbolo basándonos en el lote y el riesgo por operación.  

    Función: Cálculo del Lote Ideal Basado en el Riesgo por Operación

    La función GetIdealLot calcula el tamaño de lote ideal (nlot) considerando la pérdida máxima permitida por operación y la distancia del stop loss (StopLoss). Esto garantiza que las operaciones respeten el límite de riesgo definido por el usuario.

    void GetIdealLot(
        double& nlot,                     // Lote ideal calculado
        double glot,                      // Lote bruto (lote máximo según el balance)
        double max_risk_per_operation,    // Riesgo máximo permitido por operación (en moneda de la cuenta)
        double& new_risk_per_operation,   // Riesgo calculado para el lote ajustado (en moneda de la cuenta)
        long StopLoss                     // Distancia del Stop Loss (en puntos)
    )
    

    Descripción de los Parámetros:

    1. nlot: Será el lote ideal ajustado por la función.
    2. glot: Es el lote bruto, el máximo, que se puede abrir utilizando todos los fondos disponibles de la cuenta.
    3. max_risk_per_operation: Representa el riesgo máximo permitido por operación, expresado en la moneda de la cuenta.
    4. new_risk_per_operation: Indica el riesgo real de la operación ajustada, considerando el lote calculado (nlot). Esto representa cuánto se perdería si el precio alcanza el stop loss.
    5. StopLoss: La distancia del stop loss en puntos.

    1. Verificación Inicial

    La función verifica que el valor del "StopLoss" sea mayor a 0, ya que un stop loss inválido haría imposible calcular el riesgo.

    if(StopLoss <= 0)
    {
        Print("[ERROR SL] Stop Loss distance is less than or equal to zero, now correct the stoploss distance: ", StopLoss);
        nlot = 0.0; 
        return;   
    }
    

    2. Inicialización de Variables

    Se inicializan los valores necesarios para los cálculos posteriores:

    • spread: Spread actual del símbolo.
    • tick_value: Valor por tick, indicando cuánto representa un movimiento mínimo en moneda de la cuenta.
    • step: Incremento mínimo permitido para el tamaño de lote.
    new_risk_per_operation = 0;  // Inicializamos el nuevo riesgo
    long spread = (long)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    

    3. Cálculo del Riesgo Actual (rpo)

    Se calcula el riesgo actual por operación (rpo) usando la fórmula:

    RISK-1

    En código:

    double rpo = (glot * (spread + 1 + (StopLoss * tick_value)));
    

    4. Verificación del Riesgo Máximo

    La función evalúa si el riesgo actual (rpo) supera el riesgo máximo permitido (max_risk_per_operation):

    Caso 1: Riesgo supera el máximo permitido

    • Se ajusta el tamaño del lote proporcionalmente al riesgo máximo permitido.
    • Se redondea el lote ajustado al incremento mínimo (step).
    • Se calcula el nuevo riesgo asociado al lote ajustado.
    if(rpo > max_risk_per_operation)
    {
        double new_lot = (max_risk_per_operation / rpo) * glot;
        new_lot = MathFloor(new_lot / step) * step;
        new_risk_per_operation = new_lot * (spread + 1 + (StopLoss * tick_value));
        nlot = new_lot; 
    }
    

    Caso 2: Riesgo dentro del límite permitido

    • Si el riesgo actual no supera el límite, se conservan los valores originales:
    else
    {
        new_risk_per_operation = rpo; // Riesgo actual
        nlot = glot;                  // Lote bruto
    }
    

    Finalmente, crearemos la última función para poder calcular el stop loss basándonos en la pérdida máxima por operación y un lote designado por el usuario:

    long GetSL(const ENUM_ORDER_TYPE type , double risk_per_operation , double lot) 
    {
     long spread = (long)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
     double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
     double result = ((risk_per_operation/lot)-spread-1)/tick_value;
     
    return long(MathRound(result));
    }  

    Descripción de los Parámetros:

    1. type: Tipo de orden (compra o venta), aunque en esta función no se utiliza directamente.
    2. risk_per_operation: Pérdida máxima permitida por operación, expresada en la moneda de la cuenta.
    3. lot: Tamaño del lote definido por el usuario.

    Funcionamiento Paso a Paso

    1. Fórmula Base:

    La fórmula original para calcular el riesgo por operación rpo es:

    RISK-1

    En esta función, despejaremos el stop loss para calcular su valor a partir del rpo, el tamaño del lote y los demás factores.

    2. Despeje del stop loss:

    • Dividimos ambos lados de la ecuación entre el lote (lote):

    RISK-2

    • Restamos spread y 1 de ambos lados:

    RISK-3

    • Dividimos entre tick_value para aislar StopLoss:

    RISK-4

    Implementación en Código

    La fórmula anterior se traduce directamente en el cálculo en el cuerpo de la función:

    double result = ((risk_per_operation / lot) - spread - 1) / tick_value;
  • risk_per_operation / lot: Calcula el riesgo por unidad de lote.
  • - spread - 1: Resta el spread y el margen adicional.
  • / tick_value: Convierte el resultado a puntos, dividiendo por el valor de un tick.

  • El resultado se redondea y convierte a tipo long para cumplir con el formato requerido:

    return long(MathRound(result));
    

    Finalmente, vamos a crear dos scripts para calcular el lote ideal y el stop loss (sl) ideal según el riesgo definido por operación. Ambos scripts utilizan una lógica simple, pero eficiente para automatizar estos cálculos,basándonos en el balance de la cuenta y parámetros específicos del usuario.

    Primer Script: Cálculo del Lote Ideal


    Este script calculará el lote ideal según un porcentaje de riesgo por operación, un sl definido en puntos, y el tipo de orden.

    1. Propiedades del Script:

      • #property strict: Asegura que el código cumpla con las reglas estrictas de compilación.
      • #property script_show_inputs: Permite al usuario introducir parámetros desde la interfaz gráfica.
    2. Parámetros de Entrada (Inputs):

    input double percentage_risk_per_operation = 1.0; //Risk per operation in %
    input long   sl = 600; //Stops Loss in points
    input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; //Order Type

    Cálculo del Riesgo por Operación:

    La fórmula calcula el monto en dólares que se está dispuesto a arriesgar por operación según el porcentaje definido:

    double risk_per_operation = ((percentage_risk_per_operation/100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
    

    Llamada a la Función de Cálculo del Lote Ideal:

    GetIdealLot(new_lot, GetMaxLote(Order_Type), risk_per_operation, new_risk_per_operation, sl);
    

    Mensajes al Usuario: Se imprimen detalles sobre los valores calculados, como el lote ideal y el riesgo ajustado, en la consola y el gráfico.

    //+------------------------------------------------------------------+
    //|                             Get Lot By Risk Per Trade and SL.mq5 |
    //|                                                        Your name |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+ 
    #property copyright "Your name"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property strict
    #property script_show_inputs
    
    input double percentage_risk_per_operation = 1.0; // Risk per operation in %
    input long   sl = 600; // Stop Loss in points
    input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; // Order Type
    
    #include <Risk Management.mqh>
    
    //+------------------------------------------------------------------+
    //| Main script function                                             |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       // Calculate the maximum allowable risk per operation in account currency
       double risk_per_operation = ((percentage_risk_per_operation / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
       
       // Print input and calculated risk details
       Print("Risk Per operation: ", risk_per_operation);
       Print("SL in points: ", sl);
       Print("Order type: ", EnumToString(Order_Type));
       
       double new_lot;
       double new_risk_per_operation;
       
       // Calculate the ideal lot size
       GetIdealLot(new_lot, GetMaxLote(Order_Type), risk_per_operation, new_risk_per_operation, sl);
       
       // Check if the lot size is valid
       if (new_lot <= 0)
         {
          Print("The stop loss is too large or the risk per operation is low. Increase the risk or decrease the stop loss.");
         }
       else
         {
          // Display calculated values
          Print("Ideal Lot: ", new_lot);
          Print("Maximum loss with SL: ", sl, " | Lot: ", new_lot, " is: ", new_risk_per_operation);
          Comment("Ideal Lot: ", new_lot);
         }
       
       Sleep(1000);
       Comment(" ");
      }
    //+------------------------------------------------------------------+
    

    Segundo Script: Cálculo del sl ideal


    Este script calcula el Stop Loss en puntos según un lote definido por el usuario y el riesgo máximo por operación.

    input double percentage_risk_per_operation = 1.0; //Risk per operation in %
    input double Lot = 0.01; //lot
    input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; //Order Type

    Cálculo del sl ideal: Se utiliza la función get sl para determinar el sl en puntos:

    long new_sl = GetSL(Order_Type, risk_per_operation, Lot);
    

    Verificación del resultado: Si el sl calculado no es válido (new_sl es menor o igual que 0), se informa al usuario.

    //+------------------------------------------------------------------+
    //|                         Get Sl by risk per operation and lot.mq5 |
    //|                                                        Your name |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Your name"
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property strict
    #property script_show_inputs
    
    input double percentage_risk_per_operation = 1.0; // Risk per operation in %
    input double Lot = 0.01; // Lot size
    input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; // Order Type
    
    #include <Risk Management.mqh>
    
    //+------------------------------------------------------------------+
    //| Main script function                                             |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       // Calculate the maximum allowable risk per operation in account currency
       double risk_per_operation = ((percentage_risk_per_operation / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
       
       // Print input and calculated risk details
       Print("Risk Per operation: ", risk_per_operation);
       Print("Lot size: ", Lot);
       Print("Order type: ", EnumToString(Order_Type));
       
       // Calculate the ideal stop loss
       long new_sl = GetSL(Order_Type, risk_per_operation, Lot);
       
       // Check if the SL is valid
       if (new_sl <= 0)
         {
          Print("The lot size is too high or the risk per operation is too low. Increase the risk or decrease the lot size.");
         }
       else
         {
          // Display calculated values
          Print("For lot: ", Lot, ", and risk: ", risk_per_operation, ", the ideal SL is: ", new_sl);
          Comment("Ideal SL: ", new_sl);
         }
       
       Sleep(1000);
       Comment(" ");
      }
    //+------------------------------------------------------------------+

    Ahora, para poner en práctica el script, lo utilizaremos para obtener el lote ideal en función del riesgo por operación. Llevaremos a cabo las pruebas con el símbolo XAUUSD, que corresponde al oro.

     SCRIPT-RISK-1

    Con parámetros como un stop loss de 200 puntos y un riesgo por operación del 1.0% del balance de la cuenta, además de especificar el tipo de orden como ORDER_TYPE_BUY, el resultado será el siguiente:

     SCRIPT-RISK-2

    El resultado como se ve en la pestaña de expertos en un lote de 0.01 para 200 puntos y un riesgo por operación de 3.81 que represente el 1% de la cuenta.


    Conclusión 

    Hemos completado la primera parte de esta serie, en la cual nos hemos centrado en desarrollar las funciones principales que utilizaremos en la clase de gestión de riesgo. Estas funciones serán esenciales para obtener el profit y realizar cálculos adicionales. En la siguiente parte, exploraremos cómo integrar todo lo aprendido en una interfaz gráfica, utilizando las bibliotecas de controles de MQL5.

    Características del Wizard MQL5 que debe conocer (Parte 29): Continuación sobre las tasas de aprendizaje con MLP Características del Wizard MQL5 que debe conocer (Parte 29): Continuación sobre las tasas de aprendizaje con MLP
    Concluimos nuestro análisis de la sensibilidad de la tasa de aprendizaje al rendimiento de los Asesores Expertos examinando principalmente las Tasas de Aprendizaje Adaptativo. Estas tasas de aprendizaje pretenden personalizarse para cada parámetro de una capa durante el proceso de entrenamiento, por lo que evaluamos los beneficios potenciales frente al peaje de rendimiento esperado.
    Creación de un modelo de restricción de tendencia de velas (Parte 7): Perfeccionamos nuestro modelo de desarrollo de la EA Creación de un modelo de restricción de tendencia de velas (Parte 7): Perfeccionamos nuestro modelo de desarrollo de la EA
    En este artículo, profundizaremos en la preparación detallada de nuestro indicador para el desarrollo del Asesor Experto (EA). Nuestro debate abarcará mejoras adicionales en la versión actual del indicador para mejorar su precisión y funcionalidad. Además, introduciremos nuevas características que marcan puntos de salida, abordando una limitación de la versión anterior, que solo identificaba puntos de entrada.
    Análisis del sentimiento en Twitter con sockets Análisis del sentimiento en Twitter con sockets
    Este innovador bot comercial integra MetaTrader 5 con Python para aprovechar el análisis del sentimiento de las redes sociales en tiempo real para tomar decisiones comerciales automatizadas. Mediante el análisis del sentimiento en Twitter relacionado con instrumentos financieros específicos, el bot traduce las tendencias de las redes sociales en señales de negociación procesables. Utiliza una arquitectura cliente-servidor con comunicación por socket, lo que permite una interacción perfecta entre las capacidades de negociación de MT5 y la potencia de procesamiento de datos de Python. El sistema demuestra el potencial de combinar las finanzas cuantitativas con el procesamiento del lenguaje natural, ofreciendo un enfoque de vanguardia para el comercio algorítmico que aprovecha fuentes de datos alternativas. Si bien muestra potencial, el bot también destaca áreas para mejoras futuras, incluidas técnicas de análisis de sentimientos más avanzadas y estrategias mejoradas de gestión de riesgos.
    Kit de herramientas de negociación MQL5 (Parte 2): Ampliación e implantación de la biblioteca EX5 de gestión de posiciones Kit de herramientas de negociación MQL5 (Parte 2): Ampliación e implantación de la biblioteca EX5 de gestión de posiciones
    Aprenda a importar y utilizar bibliotecas EX5 en su código o proyectos MQL5. En este artículo de continuación, ampliaremos la biblioteca EX5 agregando más funciones de gestión de posiciones a la biblioteca existente y creando dos Asesores Expertos. El primer ejemplo utilizará el indicador técnico de promedio dinámico de índice variable para desarrollar un asesor experto en estrategia comercial de trailing stop, mientras que el segundo ejemplo utilizará un panel comercial para monitorear, abrir, cerrar y modificar posiciones. Estos dos ejemplos demostrarán cómo utilizar e implementar la biblioteca de gestión de posiciones EX5 actualizada.