English Русский 中文 Deutsch 日本語 Português
preview
Desarrollamos un asesor experto multidivisa (Parte 14): Cambio de volumen adaptable en el gestor de riesgos

Desarrollamos un asesor experto multidivisa (Parte 14): Cambio de volumen adaptable en el gestor de riesgos

MetaTrader 5Trading |
488 1
Yuriy Bykov
Yuriy Bykov

Introducción

En uno de los artículos anteriores de esta serie abordamos el tema del control de riesgos y desarrollamos una clase de gestor de riesgos que implementa la funcionalidad básica. Esta permitía establecer el nivel máximo de pérdida diaria y el nivel máximo de pérdida total; cuando se alcanzaba, la negociación se detenía y se cerraban todas las posiciones abiertas. Si se alcanzaba la pérdida diaria, la negociación se reanudaba al día siguiente, y si se alcanzaba la pérdida total, la negociación no se reanudaba en absoluto.

Recordemos que consideramos como posibles direcciones de desarrollo del gestor de riesgos un cambio más suave del tamaño de las posiciones (por ejemplo, una reducción de 2 veces cuando se supera la mitad del límite), y una recuperación más "inteligente" de los volúmenes (por ejemplo, solo cuando la pérdida supera el nivel en el que se produjo la reducción del tamaño de la posición). También podemos añadir un parámetro de beneficio objetivo máximo; una vez alcanzado este, la negociación también se detendrá. Es poco probable que este parámetro resulte útil para el comercio en una cuenta personal, pero para el comercio en las cuentas de prop-trading será muy útil, porque por lo general en estas, después de alcanzar el nivel de beneficio previsto, el comercio solo puede continuar en otra cuenta.

También hemos mencionado la introducción de restricciones comerciales basadas en el tiempo como una de las posibles direcciones de desarrollo de los gestores de riesgos, pero en el marco de este artículo no tocaremos este tema: mejor lo dejamos para el futuro. Entre tanto, intentaremos aplicar el cambio adaptativo de volúmenes en el gestor de riesgos y veremos si sirve de algo.


Caso básico

Utilizaremos el asesor experto del artículo anterior, añadiéndole la posibilidad de controlar los parámetros del gestor de riesgos. También le haremos otras pequeñas adiciones que comentaremos más adelante. Por ahora, acordaremos los parámetros del EA que utilizaremos para evaluar los resultados de los cambios realizados.

En primer lugar, fijaremos la composición específica de instancias individuales de estrategias comerciales que se utilizarán en el EA de prueba. En el parámetro passes_, especificaremos los identificadores de las mejores pasadas después de la segunda etapa de optimización para cada uno de los tres símbolos a optimizar y cada uno de los tres marcos temporales (9 identificadores en total). Cada identificador ocultará un grupo normalizado de 16 instancias únicas de estrategias comerciales. Así, el grupo final contendrá un total de 144 instancias de estrategias comerciales divididas en 9 grupos de 16 estrategias cada uno. El grupo final no se normalizará, ya que no hemos seleccionado un multiplicador normalizador para él.

En segundo lugar, utilizaremos un balance fijo para negociar igual a 10 000$ y nuestra reducción máxima esperada estándar del 10% para un factor de escala igual a 1. Esto último intentaremos cambiarlo en el rango de 1 a 10. Al mismo tiempo, también aumentará la reducción máxima permitida, pero ahora la controlará adicionalmente nuestro gestor de riesgos, que no permitirá que se supere el 10%.

Para ello, activaremos el gestor de riesgos y estableceremos un valor de la pérdida total máxima igual al 10% del balance básico de 10 000$, es decir, 1 000$. Para la pérdida máxima diaria fijaremos un valor dos veces inferior, es decir, 500$. Estos dos parámetros no cambiarán a medida que crezca el balance.

Ajustaremos todos los valores de los parámetros de entrada como hemos descrito anteriormente:

Fig. 1. Parámetros de entrada para el asesor experto de prueba con el gestor de riesgos original.

Ejecutaremos la optimización en el intervalo de 2021 y 2022 para ver cómo funciona el EA con diferentes valores del factor de escala para los tamaños de posición (scale_). Obtendremos los siguientes resultados:

Fig. 2. Resultados de la optimización del parámetro scale_ en el EA de prueba con el gestor de riesgos original.


Los resultados se clasificarán según el valor creciente del parámetro scale_, es decir, cuanto más baja sea la línea, mayores serán los tamaños de las posiciones abiertas utilizadas por el asesor experto. Se ve bien que partiendo de algún valor crítico, el resultado final será una pérdida de algo más de 1 000.

Durante el intervalo de prueba hemos encontrado varias reducciones de profundidad variable. En las pasadas en las que ninguna reducción ha hecho descender el nivel de fondos por debajo de 9 000$, la negociación ha continuado hasta el final del intervalo. Y en aquellas pasadas en las que el nivel de fondos ha caído por debajo de los 9 000$ durante la caída, la negociación se ha detenido en ese punto y nunca ha vuelto a reanudarse. En estas pasadas, ahí es donde hemos tenido una pérdida de alrededor de 1 000$. Su pequeña superación del valor calculado se explica muy probablemente por el hecho de que hemos utilizado el modo de trabajo del asesor experto solo en nuevas barras de minutos. Por consiguiente, los precios han tenido tiempo de cambiar un poco más en un minuto desde el momento en que se alcanzó exactamente la pérdida fijada hasta el momento en que el gestor de riesgos la comprobó y decidió cerrar todas las posiciones.

Estas diferencias son menores en la mayoría de las pasadas, y podemos desatenderlas acordando fijar un límite ligeramente inferior en los parámetros, o cambiando la forma de operar del gestor de riesgos según lo previsto. La único pasada que inspira preocupación es la pasada para scale_ = 5,5, cuando después de cerrar todas las posiciones la pérdida ha superado la pérdida calculada en más de un 20%, ascendiendo a unos 1 234$.

Para profundizar en el análisis, echaremos un vistazo al gráfico de las curvas de balance y fondos de la primera pasada (scale_ = 1,0). Como las demás pasadas solo difieren en el tamaño de las posiciones abiertas, sus gráficos de balance y fondos tendrán el mismo aspecto que los de la primera pasada, solo que estarán más estirados verticalmente.

Fig. 3. Resultados de una pasada con el parámetro scale_ = 1.0 en el asesor experto de prueba con el gestor de riesgos original


Vamos a comprarlos con los resultados sin el gestor de riesgos:

Fig. 4. Resultados de una pasada con el parámetro scale_ = 1.0 en el EA de prueba sin gestor de riesgos


Sin el gestor de riesgos, la rentabilidad total ha resultado ser un tanto por ciento superior, mientras que la reducción máxima de los fondos ha seguido siendo la misma. Esto demuestra que la normalización de las estrategias cuando se agrupan y su posterior trabajo conjunto ofrecen buenos resultados: el gestor de riesgos solo ha tenido que cerrar posiciones cuando la pérdida diaria supera la pérdida diaria unas tres veces en dos años.

Veamos ahora los resultados de la pasada con gestor de riesgos y scale_ = 3. El beneficio ha sido aproximadamente un 50% mayor, pero la reducción también se ha triplicado.

Fig. 5. Resultados de una pasada con el parámetro scale_ = 3.0 en el asesor experto de prueba con el gestor de riesgos original


Sin embargo, a pesar del aumento de la reducción en términos absolutos a casi 3 000$, el gestor de riesgos ha tenido que evitar que la reducción del día superare los 500$. Es decir, hemos tenido varios días consecutivos en los que el gestor de riesgos ha cerrado todas las posiciones y las ha vuelto a abrir al comienzo del día siguiente. A partir de los fondos máximos anteriores, los fondos actuales han llegado a reducirse hasta 3 000$, pero para cada día individual en relación con el balance o los fondos máximos al comienzo del día, la reducción no ha superado los 500$. Sin embargo, resulta peligroso utilizar un tamaño de posición tan elevado, ya que también existe un límite para la pérdida total. En este caso, hemos tenido suerte de que la gran reducción se haya producido poco después del inicio del periodo de prueba. El tamaño del balance ha tenido tiempo de crecer y así ha aumentado el valor de la pérdida total máxima, que era de 1 000$ en el momento del inicio y se contaba a partir del valor del balance inicial. Si el periodo de prueba hubiera comenzado justo antes de que la reducción alcanzara los 3 000$, se habría activado el límite total y se habría interrumpido la negociación.

Para considerar el impacto de una posible mala hora de inicio, cambiaremos los parámetros del gestor de riesgos para que el nivel de pérdida total se calcule a partir del último balance o los fondos máximos en lugar de a partir del balance inicial. Pero para ello, primero tendremos que hacer ciertas adiciones al código del gestor de riesgos, pues la capacidad de establecer el valor del parámetro requerido aún no se ha implementado.


Modernización de CVirtualRiskManager

Hemos planeado hacer bastantes cambios en la clase de gestor de riesgos, y muchos de ellos necesitan modificaciones en los mismos métodos. Esto dificultará un poco la descripción, ya que resulta difícil separar las ediciones relacionadas con las diferentes características que se añaden. Por eso vamos a describir la versión ya terminada del código, comenzando por el lugar más sencillo.


Actualización de las enumeraciones

Antes de describir la clase, hemos declarado algunas enumeraciones que se utilizarán más adelante. Vamos a ampliar un poco la composición de estas enumeraciones. Por ejemplo, añadiremos dos nuevos estados a la enumeración ENUM_RM_STATE que contendrán los posibles estados del gestor de riesgos:

  • RM_STATE_RESTORE — estado que se produce después del nuevo periodo diario hasta que se restablecen por completo los tamaños de las posiciones abiertas. Esta condición no se daba antes, ya que restablecíamos directamente los tamaños de las posiciones tras el nuevo día. Ahora tenemos la posibilidad de hacer esto no directamente, sino solo cuando los precios vuelvan a valores más favorables para la apertura. Veámoslo con más detalle.

  • RM_STATE_OVERALL_PROFIT — estado que se produce después de alcanzar el beneficio especificado. Tras este suceso, se interrumpirá la negociación.

Hemos convertido la antigua enumeración ENUM_RM_CALC_LIMIT en tres enumeraciones diferentes: para la pérdida diaria, para la pérdida total y para la ganancia total. Los valores de estas enumeraciones se usarán para determinar dos cosas:

  • cómo utilizar el número transmitido en los parámetros: como valor absoluto o como valor relativo dado como porcentaje de (a partir de) el nivel diario o los valores más altos del balance o los fondos;
  • a partir de (to) qué valor establecer el nivel umbral: el nivel diario o los valores más altos del balance o los fondos.

Estas opciones se especificarán en los comentarios de los valores de enumeración.

// Possible risk manager states
enum ENUM_RM_STATE {
   RM_STATE_OK,            // Limits are not exceeded 
   RM_STATE_DAILY_LOSS,    // Daily limit is exceeded
   RM_STATE_RESTORE,       // Recovery after daily limit
   RM_STATE_OVERALL_LOSS,  // Overall limit exceeded
   RM_STATE_OVERALL_PROFIT // Overall profit reached
};

// Possible methods for calculating limits
enum ENUM_RM_CALC_DAILY_LOSS {
   RM_CALC_DAILY_LOSS_MONEY_BB,    // [$] to Daily Level
   RM_CALC_DAILY_LOSS_PERCENT_BB,  // [%] from Base Balance to Daily Level
   RM_CALC_DAILY_LOSS_PERCENT_DL   // [%] from/to Daily Level
};

// Possible methods for calculating general limits
enum ENUM_RM_CALC_OVERALL_LOSS {
   RM_CALC_OVERALL_LOSS_MONEY_BB,           // [$] to Base Balance
   RM_CALC_OVERALL_LOSS_MONEY_HW_BAL,       // [$] to HW Balance
   RM_CALC_OVERALL_LOSS_MONEY_HW_EQ_BAL,    // [$] to HW Equity or Balance
   RM_CALC_OVERALL_LOSS_PERCENT_BB,         // [%] from/to Base Balance
   RM_CALC_OVERALL_LOSS_PERCENT_HW_BAL,     // [%] from/to HW Balance
   RM_CALC_OVERALL_LOSS_PERCENT_HW_EQ_BAL   // [%] from/to HW Equity or Balance
};

// Possible methods for calculating overall profit
enum ENUM_RM_CALC_OVERALL_PROFIT {
   RM_CALC_OVERALL_PROFIT_MONEY_BB,           // [$] to Base Balance
   RM_CALC_OVERALL_PROFIT_PERCENT_BB,         // [%] from/to Base Balance
};


Descripción de la clase

En la descripción de la clase CVirtualRiskManager, hemos añadido nuevas propiedades y métodos en la sección protegida. La sección pública al completo permanecerá sin cambios. Lo que hemos añadido se destaca en color de fondo verde:

//+------------------------------------------------------------------+
//| Risk management class (risk manager)                             |
//+------------------------------------------------------------------+
class CVirtualRiskManager : public CFactorable {
protected:
// Main constructor parameters
   bool              m_isActive;             // Is the risk manager active?

   double            m_baseBalance;          // Base balance

   ENUM_RM_CALC_DAILY_LOSS   m_calcDailyLossLimit; // Method of calculating the maximum daily loss
   double            m_maxDailyLossLimit;          // Parameter of calculating the maximum daily loss
   double            m_closeDailyPart;             // Threshold part of the daily loss

   ENUM_RM_CALC_OVERALL_LOSS m_calcOverallLossLimit;  // Method of calculating the maximum overall loss
   double            m_maxOverallLossLimit;           // Parameter of calculating the maximum overall loss
   double            m_closeOverallPart;              // Threshold part of the overall loss

   ENUM_RM_CALC_OVERALL_PROFIT m_calcOverallProfitLimit; // Method for calculating maximum overall profit
   double            m_maxOverallProfitLimit;            // Parameter for calculating the maximum overall profit

   double            m_maxRestoreTime;             // Waiting time for the best entry on a drawdown
   double            m_lastVirtualProfitFactor;    // Initial best drawdown multiplier


// Current state
   ENUM_RM_STATE     m_state;                // State
   double            m_lastVirtualProfit;    // Profit of open virtual positions at the moment of loss limit 
   datetime          m_startRestoreTime;     // Start time of restoring the size of open positions

// Updated values
   double            m_balance;              // Current balance
   double            m_equity;               // Current equity
   double            m_profit;               // Current profit
   double            m_dailyProfit;          // Daily profit
   double            m_overallProfit;        // Overall profit
   double            m_baseDailyBalance;     // Daily basic balance
   double            m_baseDailyEquity;      // Daily base balance
   double            m_baseDailyLevel;       // Daily base level
   double            m_baseHWBalance;        // balance High Watermark
   double            m_baseHWEquityBalance;  // equity or balance High Watermark
   double            m_virtualProfit;        // Profit of open virtual positions

// Managing the size of open positions
   double            m_baseDepoPart;         // Used (original) part of the overall balance
   double            m_dailyDepoPart;        // Multiplier of the used part of the overall balance by daily loss
   double            m_overallDepoPart;      // Multiplier of the used part of the overall balance by overall loss

// Protected methods
   double            DailyLoss();            // Maximum daily loss
   double            OverallLoss();          // Maximum overall loss

   void              UpdateProfit();         // Update current profit values
   void              UpdateBaseLevels();     // Updating daily base levels

   void              CheckLimits();          // Check for excess of permissible losses
   bool              CheckDailyLossLimit();     // Check for excess of the permissible daily loss
   bool              CheckOverallLossLimit();   // Check for excess of the permissible overall loss
   bool              CheckOverallProfitLimit(); // Check if the specified profit has been achieved

   void              CheckRestore();         // Check the need for restoring the size of open positions
   bool              CheckDailyRestore();       // Check if the daily multiplier needs to be restored
   bool              CheckOverallRestore();     // Check if the overall multiplier needs to be restored

   double            VirtualProfit();        // Determine the profit of open virtual positions
   double            RestoreVirtualProfit(); // Determine the profit of open virtual positions to restore

   void              SetDepoPart();          // Set the values of the used part of the overall balance

public:
   ...
};

Las propiedades m_closeDailyPart y m_closeOverallPart nos permitirán realizar un redimensionamiento más suave de las posiciones. Su uso es similar entre sí y la única diferencia es a qué límite (diario o total) pertenecerá cada propiedad. Por ejemplo, si establecemos m_closeDailyPart = 0,5, cuando la pérdida alcance la mitad del límite diario, el tamaño de las posiciones se reducirá a la mitad. Si la pérdida sigue aumentando y alcanza la mitad de la mitad restante del límite diario, el tamaño de las posiciones (ya reducido antes a la mitad) volverá a reducirse a la mitad.

La reducción de los tamaños de posición se realizará usando la modificación de las propiedades m_dailyDepoPart y m_overallDepoPart. Sus valores se utilizarán en el método de fijación del valor de la parte utilizable del balance total para el comercio. Estos entran en la fórmula como multiplicadores, por lo que la reducción a la mitad de cualquiera de ellos reducirá a la mitad el volumen total:

//+------------------------------------------------------------------+
//| Set the value of the used part of the overall balance            |
//+------------------------------------------------------------------+
void CVirtualRiskManager::SetDepoPart() {
   CMoney::DepoPart(m_baseDepoPart * m_dailyDepoPart * m_overallDepoPart);
}

La propiedad m_baseDepoPart usada en esta función contendrá el valor del tamaño inicial de la parte de balance utilizada para negociar.

Las propiedades m_maxRestoreTime y m_lastVirtualProfitFactor se utilizarán para determinar la posibilidad de restaurar el tamaño de las posiciones abiertas.

La primera propiedad especificará el tiempo en minutos tras el cual se restablecerá el tamaño aunque el beneficio virtual no sea negativo. Es decir, después de este tiempo el asesor experto reabrirá las posiciones reales de mercado correspondientes a las posiciones virtuales, incluso si durante el tiempo en que se han cerrado las posiciones reales, los precios han ido en la dirección correcta y el beneficio virtual ha llegado a ser mayor que en el momento en que se han alcanzado los límites. Hasta entonces, la recuperación del volumen solo se producirá si el beneficio calculado de las posiciones virtuales es inferior a algún valor calculado que cambie con el tiempo.

La segunda propiedad especificará un multiplicador que determinará cuántas veces la pérdida de las posiciones virtuales al inicio de un nuevo periodo diario deberá ser mayor que la pérdida de las posiciones virtuales cuando se han alcanzado los límites para que las posiciones se restablezcan inmediatamente. Por ejemplo, un valor de 1 para este parámetro significará que el redimensionamiento al principio del día solo se producirá si las posiciones virtuales se han mantenido en el mismo nivel o han entrado en una reducción aún mayor en comparación con la última vez que se han alcanzado los límites.

También cabe destacar que ahora el momento de alcanzar el límite se considerará no solo cuando la reducción supere el límite establecido, sino también, por ejemplo, cuando alcance la mitad del límite diario, si el parámetro m_closeDailyPart es igual a 0,5.


Cálculo de la rentabilidad de las posiciones virtuales

Al aplicar cierres parciales y redimensionar aún más las posiciones abiertas, tendremos que determinar correctamente cuál habría sido el beneficio o la pérdida flotante en este punto si todas las posiciones abiertas por la estrategia hubieran continuado abiertas. Por lo tanto, hemos añadido a la clase de gestor de riesgos un método para calcular el beneficio actual de las posiciones virtuales abiertas. Estas no se cerrarán cuando se alcancen los límites de la reducción, así que podremos determinar en cualquier momento qué beneficio aproximado habrían tenido las posiciones abiertas en el mercado correspondientes a las posiciones virtuales. Este valor calculado no se corresponderá exactamente con la realidad debido a la ausencia de comisiones y swaps, pero para nuestros fines esta precisión será suficiente.

La última vez no necesitamos este método para ningún cálculo de cuyos resultados dependan las acciones posteriores. Ahora ya lo necesitamos, y para calcular correctamente el beneficio de las posiciones virtuales tendremos que hacerle pequeñas adiciones. La cuestión es que el método usado para determinar el beneficio de una posición virtual CMoney::Profit() utilizará para el cálculo el valor del multiplicador de uso del balance CMoney::DepoPart(). Si hemos reducido este multiplicador, el cálculo del beneficio virtual se realizará para los tamaños de posición reducidos, no para los tamaños que había antes de la reducción.

Nos interesa el beneficio para los tamaños de posición iniciales, por lo que antes de calcularlo retornaremos temporalmente el multiplicador de uso del balance inicial. Después, calcularemos el beneficio de las posiciones virtuales y volveremos a establecer el multiplicador de uso del balance actual llamando al método SetDepoPart():

//+------------------------------------------------------------------+
//| Determine the profit of open virtual positions                   |
//+------------------------------------------------------------------+
double CVirtualRiskManager::VirtualProfit() {
// Access the receiver object
   CVirtualReceiver *m_receiver = CVirtualReceiver::Instance();

// Set the initial balance usage multiplier
   CMoney::DepoPart(m_baseDepoPart);

   double profit = 0;

// Find the profit sum for all virtual positions
   FORI(m_receiver.OrdersTotal(), profit += CMoney::Profit(m_receiver.Order(i)));

// Restore the current balance usage multiplier
   SetDepoPart();

   return profit;
}


High Watermark

Para un mejor análisis, nos gustaría añadir la posibilidad de contar el nivel máximo de pérdidas no solo a partir del balance inicial de la cuenta, sino también, por ejemplo, a partir del último balance máximo alcanzado. Por ello, hemos añadido m_baseHWBalance y m_baseHWEquityBalance a las propiedades que deberán actualizarse constantemente. En el método UpdateProfit(), hemos añadido su cálculo con una comprobación de que la rentabilidad total se calcule exactamente en relación con los valores más altos del balance o de los fondos, no del balance básico:

//+------------------------------------------------------------------+
//| Updating current profit values                                   |
//+------------------------------------------------------------------+
void CVirtualRiskManager::UpdateProfit() {
// Current equity
   m_equity = AccountInfoDouble(ACCOUNT_EQUITY);

// Current balance
   m_balance = AccountInfoDouble(ACCOUNT_BALANCE);

// Maximum balance (High Watermark)
   m_baseHWBalance = MathMax(m_balance, m_baseHWBalance);

// Maximum balance or equity (High Watermark)
   m_baseHWEquityBalance = MathMax(m_equity, MathMax(m_balance, m_baseHWEquityBalance));

// Current profit
   m_profit = m_equity - m_balance;

// Current daily profit relative to the daily level
   m_dailyProfit = m_equity - m_baseDailyLevel;

// Current overall profit relative to base balance
   m_overallProfit = m_equity - m_baseBalance;

// If we take the overall profit relative to the highest balance,
   if(m_calcOverallLossLimit       == RM_CALC_OVERALL_LOSS_MONEY_HW_BAL
         || m_calcOverallLossLimit == RM_CALC_OVERALL_LOSS_PERCENT_HW_BAL) {
      // Recalculate it
      m_overallProfit = m_equity - m_baseHWBalance;
   }

// If we take the overall profit relative to the highest balance or equity,
   if(m_calcOverallLossLimit       == RM_CALC_OVERALL_LOSS_MONEY_HW_EQ_BAL
         || m_calcOverallLossLimit == RM_CALC_OVERALL_LOSS_PERCENT_HW_EQ_BAL) {
      // Recalculate it
      m_overallProfit = m_equity - m_baseHWEquityBalance;
   }

// Current profit of virtual open positions
   m_virtualProfit = VirtualProfit();

   ...
}


Método de comprobación de los límites

Este método también ha sufrido cambios: hemos dividido su código en varios métodos auxiliares, por lo que ahora tendrá el aspecto que sigue:

//+------------------------------------------------------------------+
//| Check loss limits                                                |
//+------------------------------------------------------------------+
void CVirtualRiskManager::CheckLimits() {
   if(false
         || CheckDailyLossLimit()     // Check daily limit
         || CheckOverallLossLimit()   // Check overall limit
         || CheckOverallProfitLimit() // Check overall profit
     ) {
      // Remember the current level of virtual profit
      m_lastVirtualProfit = m_virtualProfit;

      // Notify the recipient about changes
      CVirtualReceiver::Instance().Changed();
   }
}

En el método de comprobación del límite diario de pérdidas, se comprobará en primer lugar si se ha alcanzado el límite diario o una parte determinada del mismo, definida mediante el parámetro m_closeDailyPart. Si es así, disminuiremos el multiplicador de la parte utilizada del balance total por la pérdida diaria. Si ya es demasiado pequeño, lo pondremos a cero. A continuación, fijaremos el valor de la parte utilizada del balance total y estableceremos el gestor de riesgos en el estado de la pérdida diaria alcanzada.

//+------------------------------------------------------------------+
//| Check daily loss limit                                           |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckDailyLossLimit() {
// If daily loss is reached and positions are still open
   if(m_dailyProfit < -DailyLoss() * (1 - m_dailyDepoPart * (1 - m_closeDailyPart))
      && CMoney::DepoPart() > 0) {

      // Reduce the multiplier of the used part of the overall balance by the daily loss
      m_dailyDepoPart *= (1 - m_closeDailyPart);

      // If the multiplier is already too small,
      if(m_dailyDepoPart < 0.05) {
         // Set it to 0
         m_dailyDepoPart = 0;
      }

      // Set the value of the used part of the overall balance
      SetDepoPart();
    
      ... 

      // Set the risk manager to the achieved daily loss state
      m_state = RM_STATE_DAILY_LOSS;

      return true;
   }

   return false;
}

El método de comprobación del límite de pérdida total funciona de forma similar, solo que la transferencia del gestor de riesgos al estado de pérdida total alcanzada se producirá únicamente si el multiplicador de la parte utilizada del balance total para la pérdida total ha pasado a ser igual a cero:

//+------------------------------------------------------------------+
//| Check the overall loss limit                                     |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckOverallLossLimit() {
// If overall loss is reached and positions are still open
   if(m_overallProfit < -OverallLoss() * (1 - m_overallDepoPart * (1 - m_closeOverallPart))
         && CMoney::DepoPart() > 0) {
      // Reduce the multiplier of the used part of the overall balance by the overall loss
      m_overallDepoPart *= (1 - m_closeOverallPart);

      // If the multiplier is already too small,
      if(m_overallDepoPart < 0.05) {
         // Set it to 0
         m_overallDepoPart = 0;

         // Set the risk manager to the achieved overall loss state
         m_state = RM_STATE_OVERALL_LOSS;
      }

      // Set the value of the used part of the overall balance
      SetDepoPart();
      
      ...

      return true;
   }

   return false;
}

Aún más sencilla será la comprobación de logro de un beneficio determinado: cuando se alcance, pondremos a cero el multiplicador correspondiente y ajustaremos el gestor de riesgos al estado del beneficio total alcanzado:

//+------------------------------------------------------------------+
//| Check if the specified profit has been achieved                  |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckOverallProfitLimit() {
// If overall loss is reached and positions are still open
   if(m_overallProfit > m_maxOverallProfitLimit && CMoney::DepoPart() > 0) {
      // Reduce the multiplier of the used part of the overall balance by the overall loss
      m_overallDepoPart = 0;

      // Set the risk manager to the achieved overall profit state
      m_state = RM_STATE_OVERALL_PROFIT;

      // Set the value of the used part of the overall balance
      SetDepoPart();
      
      ... 

      return true;
   }

   return false;
}

Los tres métodos retornarán un valor booleano que responderá a la pregunta: ¿se ha alcanzado el límite correspondiente? Si es así, en el método CheckLimits() recordaremos el nivel de beneficio virtual actual y notificaremos al receptor de volúmenes de posiciones de mercado si se producido algún cambio en la composición de la posición.

En el método de actualización de los niveles básicos diarios, hemos añadido el recálculo del beneficio diario y la transición a un estado de recuperación si previamente se ha alcanzado el límite de pérdidas diarias:

//+------------------------------------------------------------------+
//| Update daily base levels                                         |
//+------------------------------------------------------------------+
void CVirtualRiskManager::UpdateBaseLevels() {
// Update balance, funds and base daily level
   m_baseDailyBalance = m_balance;
   m_baseDailyEquity = m_equity;
   m_baseDailyLevel = MathMax(m_baseDailyBalance, m_baseDailyEquity);

   m_dailyProfit = m_equity - m_baseDailyLevel;

   ...

// If the daily loss level was reached earlier, then
   if(m_state == RM_STATE_DAILY_LOSS) {
      // Switch to the state of restoring the sizes of open positions
      m_state = RM_STATE_RESTORE;

      // Remember restoration start time
      m_startRestoreTime = TimeCurrent();
   }
}


Restablecemos el tamaño de las posiciones

El método que hacía el trabajo antes también lo dividiremos en varios métodos. En el nivel superior, llamaremos al método para comprobar si es necesaria la recuperación:

//+------------------------------------------------------------------+
//| Check the need for restoring the size of open positions          |
//+------------------------------------------------------------------+
void CVirtualRiskManager::CheckRestore() {
// If we need to restore the state to normal, then
   if(m_state == RM_STATE_RESTORE) {
      // Check the possibility of restoring the daily loss multiplier to normal 
      bool dailyRes = CheckDailyRestore();

      // Check the possibility of restoring the overall loss multiplier to normal
      bool overallRes = CheckOverallRestore();

      // If at least one of them has recovered,
      if(dailyRes || overallRes) {
         
...

         // Set the value of the used part of the overall balance
         SetDepoPart();

         // Notify the recipient about changes
         CVirtualReceiver::Instance().Changed();

         // If both multipliers are restored to normal,
         if(dailyRes && overallRes) {
            // Set normal state
            m_state = RM_STATE_OK;
         }
      }
   }
}

Los métodos auxiliares para comprobar si necesitamos recuperar el multiplicador diario y común ahora funcionan igual, pero podremos hacerlos diferentes en el futuro. Por ahora, están comprobando si el valor del beneficio virtual actual es inferior al nivel deseado. Si es así, nos convendría reabrir las posiciones reales a los precios actuales, por lo que deberemos redimensionarlas:

//+------------------------------------------------------------------+
//| Check if the daily multiplier needs to be restored               |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckDailyRestore() {
// If the current virtual profit is less than the one desired for recovery,
   if(m_virtualProfit <= RestoreVirtualProfit()) {
      // Restore the daily loss multiplier
      m_dailyDepoPart = 1.0;
      return true;
   }

   return false;
}

//+------------------------------------------------------------------+
//| Check if the overall multiplier needs to be restored             |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckOverallRestore() {
// If the current virtual profit is less than the one desired for recovery,
   if(m_virtualProfit <= RestoreVirtualProfit()) {
      // Restore the overall loss multiplier
      m_overallDepoPart = 1.0;
      return true;
   }

   return false;
}

El método RestoreVirtualProfit() calculará el nivel de beneficio virtual deseado. Utilizaremos una interpolación lineal simple con dos parámetros: cuanto más tiempo pase, menos favorable será el nivel que aceptamos para reabrir las posiciones reales.

//+------------------------------------------------------------------+
//| Determine the profit of virtual positions for recovery           |
//+------------------------------------------------------------------+
double CVirtualRiskManager::RestoreVirtualProfit() {
// If the maximum recovery time is not specified,
   if(m_maxRestoreTime == 0) {
      // Return the current value of the virtual profit
      return m_virtualProfit;
   }

// Find the elapsed time since the start of recovery in minutes
   double t = (TimeCurrent() - m_startRestoreTime) / 60.0;

// Return the calculated value of the desired virtual profit
// depending on the time elapsed since the start of recovery
   return m_lastVirtualProfit * m_lastVirtualProfitFactor * (1 - t / m_maxRestoreTime);
}

Guardaremos los cambios realizados en el archivo VirtualRiskManager.mqh en la carpeta actual.


Simulación

En primer lugar, ejecutaremos el asesor experto con parámetros que establecerán el cierre total de las posiciones cuando se alcancen los límites diarios y totales. Los resultados deberían ser los mismos que en la fig. 3.

Fig. 6. Resultados del asesor experto con el parámetro scale_ = 1.0 con ajustes como el gestor de riesgos original

Los resultados para la reducción del riesgo coinciden totalmente, para el beneficio y el beneficio medio anual normalizado (resultado de OnTester) el resultado es ligeramente superior al esperado. Bueno, eso es tranquilizador: en el proceso de edición no hemos estropeado lo que se había hecho antes.

Ahora veamos qué ocurre si establecemos el cierre adaptativo de posiciones al alcanzarse parte de los límites diario y total con un valor de 0,5:

Fig. 7. Resultados del asesor experto con los parámetros scale_ = 1.0, rmCloseDailyPart_ = 0.5, rmCloseOverallPart_ = 0.5

La reducción ha disminuido ligeramente, lo que ha provocado un ligero aumento del beneficio medio anual normalizado. Vamos a intentar ahora introducir la recuperación del tamaño de la posición con el mejor precio. Fijaremos en los parámetros el tiempo de espera para la mejor entrada en la reducción, igual a 1440 minutos (1 día), y tomaremos el multiplicador de la mejor reducción inicial, igual a 1,5. Son simplemente valores tomados de forma intuitiva.

Fig. 8. Resultados del EA con los parámetros scale_ = 1.0, rmCloseDailyPart_ = 0.5, rmCloseOverallPart_ = 0.5, rmMaxRestoreTime_ = 1440, rmLastVirtualProfitFactor_ = 1.5

El resultado del beneficio medio anual normalizado ha mejorado algo más. Parece que el esfuerzo de poner en marcha este mecanismo merecerá la pena.

Ahora vamos a intentar aumentar el tamaño de las posiciones abiertas tres veces sin cambiar los parámetros del gestor de riesgos de la última ejecución. Esto debería dar lugar a una activación más frecuente del gestor de riesgos. A ver qué tal se las arregla con un reto así.

Fig. 9. Resultados del EA con los parámetros scale_ = 3.0, rmCloseDailyPart_ = 0.5, rmCloseOverallPart_ = 0.5, rmMaxRestoreTime_ = 1440, rmLastVirtualProfitFactor_ = 1.5

En comparación con una ejecución similar con el gestor de riesgos original, también se observa una mejora de los resultados en todos los ámbitos. En comparación con la última serie, los beneficios se han duplicado, pero las pérdidas se han triplicado, lo cual reduce el valor de la rentabilidad media anual normalizada. Sin embargo, para cada día comercial, la reducción no ha superado el valor establecido en los parámetros del gestor de riesgos.

También resulta interesante ver qué ocurrirá si el tamaño de las posiciones abiertas se incrementa aún más, por ejemplo, en un factor de 10.

Fig. 10. Resultados del EA con los parámetros scale_ = 3.0, rmCloseDailyPart_ = 0.5, rmCloseOverallPart_ = 0.5, rmMaxRestoreTime_ = 1440, rmLastVirtualProfitFactor_ = 1.5

Como podemos ver, con estos tamaños de posición, el gestor de riesgos ya no ha sido capaz de mantener la pérdida total máxima fijada, y la negociación se ha detenido. Por lo tanto, no será necesario actuar así, el gestor de riesgos no es una herramienta universal que permita evitar la reducción máxima establecida en cualquier circunstancia de la estrategia comercial. Simplemente complementa las normas obligatorias de gestión de capital que deben estar presentes en una estrategia comercial.

Por último, intentaremos encontrar los mejores parámetros de gestión del riesgo utilizando la optimización genética. Lamentablemente, cada pasada tarda unos tres minutos para un intervalo de dos años, por lo que tendremos que esperar un tiempo considerable para completar la optimización. Como criterio de optimización elegiremos un criterio personalizado que calcule el beneficio medio anual normalizado. Al cabo de un rato, la parte superior de la tabla con los resultados de la optimización tiene este aspecto:

Fig. 11. Resultados de la optimización de la EA con gestor de riesgos

Como podemos observar, la selección de unos buenos parámetros de gestión del riesgo no solo puede proteger la negociación contra un aumento de las reducciones, sino también mejorar los resultados comerciales: la diferencia para las distintas pasadas ha sido de hasta un 20% de beneficios adicionales. Aunque incluso el mejor conjunto de parámetros ya encontrado es ligeramente inferior a los resultados obtenidos en la fig. 8 con valores seleccionados intuitivamente. Es probable que una mayor optimización pueda mejorar un poco más este resultado, pero no es realmente necesario.


Conclusión

Resumamos brevemente. Hemos mejorado un componente importante de cualquier sistema comercial exitoso, el gestor de riesgos, haciéndolo más flexible y personalizable. Ahora disponemos de un mecanismo que hace cumplir con mayor precisión los límites de la reducción de fondos permitida.

Aunque hemos intentado que resulte lo más fiable posible, deberemos utilizar sus funciones con prudencia. Debemos recordar que se trata de un último recurso para la protección del capital, por lo que deberemos intentar seleccionar los parámetros comerciales de forma que el gestor de riesgos no tenga que interferir en absoluto en la negociación o que lo haga con muy poca frecuencia. Como han demostrado las pruebas, cuando se superan los tamaños de posición recomendados, la velocidad de cambio de los fondos de la cuenta puede ser tan brusca que ni siquiera el gestor de riesgos tenga tiempo de evitar que la cuenta supere el nivel de reducción especificado.

Durante la elaboración de esta actualización del gestor de riesgos han surgido nuevas ideas para mejorarlo, pero un exceso de complejidad tampoco es especialmente bueno. Por consiguiente, pospondremos de momento el trabajo sobre el gestor de riesgos y volveremos en las próximas partes a cuestiones más apremiantes del desarrollo de asesores comerciales.

¡Gracias por su atención y hasta la próxima!

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/15085

Archivos adjuntos |
Cristian-bogdan Buzatu
Cristian-bogdan Buzatu | 14 ene 2025 en 23:09
Hola, es un trabajo muy emocionante, ¡gracias por su tiempo y generosidad! ¿Podría por favor poner juntos en un archivo zip todo lo que necesitamos con el fin de compilar con éxito su última versión en este script?
Aprendiendo MQL5 de principiante a profesional (Parte III): Tipos de datos complejos y archivos de inclusión Aprendiendo MQL5 de principiante a profesional (Parte III): Tipos de datos complejos y archivos de inclusión
Este artículo es el tercero de una serie de materiales sobre los principales aspectos de la programación en MQL5. Aquí nos encargaremos de tipos de datos complejos que no describimos en el artículo anterior, como estructuras, uniones, clases y el tipo de datos "función". También veremos cómo añadir modularidad a nuestro programa utilizando la directiva #include del preprocesador.
Estrategia de trading del SP500 en MQL5 para principiantes Estrategia de trading del SP500 en MQL5 para principiantes
Descubra cómo aprovechar MQL5 para pronosticar el S&P 500 con precisión, combinando análisis técnico clásico para lograr mayor estabilidad y algoritmos con principios probados en el tiempo para obtener información sólida del mercado.
Visualización de transacciones en un gráfico (Parte 1): Seleccionar un periodo para el análisis Visualización de transacciones en un gráfico (Parte 1): Seleccionar un periodo para el análisis
Aquí vamos a desarrollar un script desde cero que simplifica la descarga de pantallas de impresión de transacciones para analizar entradas comerciales. Toda la información necesaria sobre una única operación se puede mostrar cómodamente en un gráfico con la posibilidad de dibujar diferentes marcos temporales.
Trading con spreads en el mercado Fórex utilizando el factor de estacionalidad Trading con spreads en el mercado Fórex utilizando el factor de estacionalidad
El en presente artículo analizaremos las posibilidades de formar y proporcionar datos sobre el uso del factor de estacionalidad al negociar con spreads en el mercado Fórex.