
Desarrollamos un asesor experto multidivisa (Parte 14): Cambio de volumen adaptable en el gestor de riesgos
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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.






- 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