preview
Un motor de riesgo adaptativo en MQL5: Ajustar el tamaño según racha, volatilidad y drawdown

Un motor de riesgo adaptativo en MQL5: Ajustar el tamaño según racha, volatilidad y drawdown

MetaTrader 5 — Sistemas comerciales |
42 0
Martin Alejandro Bamonte
Martin Alejandro Bamonte

Contenido

  1. El problema: el porcentaje fijo no mira el contexto
  2. La base: cuánto arriesgar a partir del stop
  3. Factor uno: la racha de pérdidas
  4. Factor dos: la volatilidad del momento
  5. Factor tres: la caída de capital de la cuenta
  6. Un método por modo, para poder medir cada factor
  7. Juntar todo en una sola llamada
  8. El banco de pruebas: un asesor con el modo conmutable
  9. Metodología de la prueba
  10. Resultados: seis formas de dimensionar las mismas operaciones
  11. Las curvas de capital
  12. La caída de capital, factor por factor
  13. Qué mejoró y qué empeoró: el compromiso honesto
  14. El riesgo de sobreoptimizar los parámetros
  15. Cómo enchufarlo a un asesor real
  16. Archivos e instalación
  17. Limitaciones
  18. Conclusión



El problema: el porcentaje fijo no mira el contexto

Casi todos los asesores que veo arriesgan un porcentaje fijo del balance en cada operación, el clásico uno o dos por ciento. Es una regla sana, mucho mejor que un lote fijo, y por eso se repite en todos lados. Pero tiene un punto ciego: arriesga lo mismo sin importar lo que esté pasando alrededor de la operación.

Arriesga lo mismo cuando el mercado está quieto que cuando está roto por una noticia. Arriesga lo mismo en la primera operación de una racha mala que en la quinta, cuando la cuenta ya viene golpeada. Y arriesga lo mismo con la cuenta en su punto más alto que en plena caída, que es justo cuando conviene bajar la exposición para no cavar más hondo. El tamaño fijo trata a todas las operaciones como si fueran iguales, y no lo son.

La idea de este artículo es simple: primero calcular el tamaño por riesgo y luego reducirlo según el contexto. Tres factores concretos y medibles recortan el lote cuando las condiciones empeoran y lo dejan intacto cuando todo está normal: la racha reciente de pérdidas, la volatilidad actual frente a su promedio y la caída de capital de la cuenta. Nunca lo agrandan por encima del riesgo base: solo lo protegen.

Al terminar tendrás una clase reutilizable que se integra en cualquier asesor. Además, tendrás la prueba de qué aporta cada factor y qué no. Una idea no vale hasta que se mide. Por eso, buena parte del artículo es un banco de pruebas: ejecuta la misma estrategia con seis formas de calcular el lote y compara los resultados numéricamente.

Aclaración: este es un artículo técnico y de ingeniería. No es una estrategia ni consejo financiero, y nada acá promete rentabilidad.


La base: cuánto arriesgar a partir del stop

Antes de cualquier ajuste hay que tener el tamaño base bien calculado, y la única forma correcta es a partir de la distancia del stop. El razonamiento es directo: decides cuánto dinero estás dispuesto a perder en la operación, un porcentaje del balance, y a partir de la distancia hasta el stop calculas cuántos lotes hacen que esa distancia valga exactamente ese dinero.

//+------------------------------------------------------------------+
//| The plain risk-based lot, before any context scaling             |
//+------------------------------------------------------------------+
double CRiskEngine::BaseLots(const double stopDistance)
  {
   double balance  = AccountInfoDouble(ACCOUNT_BALANCE);
   double tickVal  = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE);
   if(stopDistance <= 0.0 || tickVal <= 0.0 || tickSize <= 0.0)
      return(0.0);
   double riskMoney  = balance * m_riskPct / 100.0;
   double lossPerLot = (stopDistance / tickSize) * tickVal;
   if(lossPerLot <= 0.0)
      return(0.0);
   return(riskMoney / lossPerLot);
  }

El detalle que arruina muchos cálculos caseros es convertir la distancia del stop a dinero. No alcanza con multiplicar puntos por un valor fijo, porque el valor de un punto cambia entre símbolos y entre brokers. La forma correcta usa SYMBOL_TRADE_TICK_VALUE y SYMBOL_TRADE_TICK_SIZE: la pérdida por lote es la distancia en ticks multiplicada por lo que vale cada tick. Con eso, el lote base es el dinero que arriesgas dividido por lo que se pierde por lote. Este mismo cálculo se adapta a la mayoría de los símbolos sin tocar nada, porque toma el valor del tick del propio símbolo en lugar de un número fijo. Con una salvedad para ciertos instrumentos, que va enseguida.

Una salvedad honesta sobre ese valor. Para la mayoría de los pares y los brokers estándar, SYMBOL_TRADE_TICK_VALUE alcanza y sobra. En algunos instrumentos, sin embargo, ese valor puede ser no trivial o depender de la moneda de la ganancia, como en ciertos CFD, futuros o símbolos exóticos. Si vas a montar esto en un instrumento raro, conviene validar antes la especificación del símbolo en lugar de confiar a ciegas en un solo campo.

Hay algo que este cálculo revela y que conviene tener presente desde el arranque, porque es la clave de todo lo que viene después. El lote base no es un número que uno elige: es una consecuencia del riesgo aceptado y de la distancia del stop. Si el stop es chico en términos de dinero, el mismo dos por ciento puede pedir un lote enorme, y ahí es donde el porcentaje fijo, que parece prudente, se vuelve peligroso. Lo vamos a ver medido más adelante.

Hay un caso de borde que conviene mirar desde el principio. Si la distancia del stop es cero, o si el broker todavía no devolvió el valor y el tamaño del tick, la función corta y devuelve cero en lugar de arriesgar un cálculo sin sentido. Es preferible no operar antes que abrir una posición con un tamaño calculado sobre datos incompletos.


Factor uno: la racha de pérdidas

El primer ajuste mira cuántas operaciones perdedoras seguidas viene teniendo la cuenta en ese símbolo. La idea no es supersticiosa: en una racha mala conviene achicar el paso, tanto para proteger el capital como para no amplificar un problema si la estrategia entró en un régimen que no le sienta.

//+------------------------------------------------------------------+
//| Count the consecutive losing trades of this symbol, newest first |
//+------------------------------------------------------------------+
int CRiskEngine::LossStreak(void)
  {
   if(!HistorySelect(0, TimeCurrent()))
      return(0);
   int streak = 0;
   int total  = HistoryDealsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = HistoryDealGetTicket(i);
      if(ticket == 0)
         continue;
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) != DEAL_ENTRY_OUT)
         continue;
      if(HistoryDealGetString(ticket, DEAL_SYMBOL) != m_symbol)
         continue;
      double net = HistoryDealGetDouble(ticket, DEAL_PROFIT)
                   + HistoryDealGetDouble(ticket, DEAL_SWAP)
                   + HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      if(net < 0.0)
         streak++;
      else
         break;   // the streak ends at the first non-losing trade
     }
   return(streak);
  }

La función recorre el historial cerrado desde la operación más reciente hacia atrás con HistorySelect y va contando pérdidas hasta encontrar la primera operación que no fue pérdida, donde corta. Dos detalles importan. Primero, solo cuenta las operaciones de cierre, con DEAL_ENTRY_OUT, para no mezclar aperturas con cierres. Segundo, el resultado de cada operación es la ganancia más el swap más la comisión, porque una operación con ganancia bruta chica puede terminar en pérdida real una vez descontados los costos.

Conviene ser preciso sobre qué cuenta esta función: cuenta operaciones de cierre, no posiciones. Si una posición se cierra en varias operaciones parciales, cada cierre parcial pesa por separado en la racha, y eso puede diferir de un conteo donde una posición equivale a una sola unidad. Para la estrategia de prueba, que cierra cada posición de una vez, ambos conteos coinciden; en un asesor con cierres parciales conviene tenerlo presente.


Factor dos: la volatilidad del momento

El segundo ajuste compara la volatilidad actual con la habitual. Cuando el rango se ensancha, el ruido crece, los stops se tocan con más facilidad y el mismo riesgo en dinero se vuelve una apuesta más incierta. Bajar el tamaño en esos momentos es una forma de mantener constante la exposición real al ruido.

//+------------------------------------------------------------------+
//| Current ATR divided by its own average: >1 means high volatility |
//+------------------------------------------------------------------+
double CRiskEngine::VolRatio(void)
  {
   double atr[];
   ArraySetAsSeries(atr, true);
   if(CopyBuffer(m_atrHandle, 0, 0, m_atrAvg, atr) < m_atrAvg)
      return(1.0);
   double sum = 0.0;
   for(int i = 0; i < m_atrAvg; i++)
      sum += atr[i];
   double avg = sum / m_atrAvg;
   if(avg <= 0.0)
      return(1.0);
   return(atr[0] / avg);
  }

La medida es un cociente: el ATR actual dividido por el promedio del propio ATR en las últimas barras. Un cociente de uno significa volatilidad normal, y el motor no toca nada. Un cociente de uno y medio significa que el mercado se mueve un cincuenta por ciento más de lo habitual, y el motor divide el tamaño por ese mismo factor. Usar el ATR contra su propio promedio, en vez de un umbral fijo, hace que el ajuste se adapte solo a cada símbolo y a cada época, sin necesidad de calibrar un número mágico. Vale aclarar que este cociente es una heurística de sentido común, no un modelo formal del riesgo real: aproxima la idea de que más movimiento pide menos tamaño, y nada más que eso.


Factor tres: la caída de capital de la cuenta

El tercer ajuste mira la salud de la cuenta entera. Mientras la cuenta hace nuevos máximos, no hay nada que corregir. Pero cuando cae por debajo de su punto más alto, cada operación siguiente cava sobre un pozo, y ahí conviene aflojar hasta recuperar terreno.

//+------------------------------------------------------------------+
//| Current drawdown as a percent of the running equity peak         |
//+------------------------------------------------------------------+
double CRiskEngine::DrawdownPct(void)
  {
   double eq = AccountInfoDouble(ACCOUNT_EQUITY);
   if(eq > m_peakEquity)
      m_peakEquity = eq;
   if(m_peakEquity <= 0.0)
      return(0.0);
   return(100.0 * (m_peakEquity - eq) / m_peakEquity);
  }

El cálculo lleva un máximo móvil del capital: cada vez que el patrimonio supera el máximo guardado, lo actualiza; la caída es cuánto está por debajo de ese máximo, en porcentaje. Detalle importante: la medida usa el patrimonio (equity) y no el saldo (balance). Por eso también reacciona a la ganancia o pérdida flotante de posiciones abiertas, no solo a operaciones cerradas. Es a propósito, porque un motor de riesgo debería reaccionar al pozo real de la cuenta en el momento, no al que quedó registrado en el saldo. El motor no hace nada hasta cruzar un umbral configurable, por ejemplo un diez por ciento, y a partir de ahí recorta el tamaño en proporción a lo que se hundió.


Un método por modo, para poder medir cada factor

Acá está el cambio central de esta herramienta. Para poder probar de verdad qué aporta cada factor, el motor no expone solo el cálculo completo, sino un método por modo. Cada factor se escribe primero como un multiplicador que vale entre cero y uno, y después cada método arma el lote a partir de la misma base aplicando solo el factor que nombra. Así, cualquier diferencia en una prueba se le puede atribuir a ese factor y a ningún otro.

//+------------------------------------------------------------------+
//| Streak factor: more consecutive losses -> smaller size (>=10%)   |
//+------------------------------------------------------------------+
double CRiskEngine::MulStreak(void)
  {
   int    streak = LossStreak();
   double m = 1.0 - m_streakStep * MathMin(streak, m_streakCap);
   return(m < 0.1 ? 0.1 : m);
  }

El multiplicador de la racha recorta un paso por cada pérdida seguida, hasta un tope, y nunca baja del diez por ciento para no anular la operación. Los otros dos siguen la misma forma: el de volatilidad devuelve uno dividido el cociente del ATR cuando hay más volatilidad que la normal, y el de la caída recorta en proporción a lo que se hundió la cuenta una vez pasado el umbral. Con los tres multiplicadores listos, los métodos de cada modo son de una línea. El de la racha, por ejemplo, parte del lote base y le aplica solo el multiplicador de la racha.

//+------------------------------------------------------------------+
//| Streak-only mode: base lot scaled by the losing-streak factor    |
//+------------------------------------------------------------------+
double CRiskEngine::LotsStreakOnly(const double stopDistance)
  {
   return(NormalizeLot(BaseLots(stopDistance) * MulStreak()));
  }

Los de volatilidad y caída son idénticos, cada uno con su propio multiplicador. Hay además un método que aplica solo la base sin ningún ajuste, que es el porcentaje fijo de toda la vida, y otro que aplica los tres a la vez. Esa separación es lo que hace posible una comparación justa: no se trata de mostrar que todo junto parece mejor, sino de ver cuánto aporta cada pieza por su cuenta.


Juntar todo en una sola llamada

Los tres factores se combinan multiplicando. Cada uno vale uno cuando no hay nada que corregir, así que en condiciones normales el resultado es igual al lote base. Cuando alguno se activa, recorta; cuando varios se activan juntos, los recortes se acumulan, que es justo lo que uno quiere en el peor escenario.

riskengine_concept

El cálculo del tamaño: el lote base sale del riesgo y la distancia del stop, y después cada factor de contexto lo puede achicar. Cada factor vale uno cuando no hay nada que corregir, así que en condiciones normales el lote queda entero.

//+------------------------------------------------------------------+
//| All-factors mode: base lot scaled by streak, volatility and DD   |
//+------------------------------------------------------------------+
double CRiskEngine::LotsAll(const double stopDistance)
  {
   return(NormalizeLot(BaseLots(stopDistance) * MulStreak() * MulVol() * MulDrawdown()));
  }

El último paso, y el que más se olvida, es dejar el lote en un valor que el broker acepte. Un tamaño calculado con decimales finos se rechaza si no respeta el paso del símbolo, o si queda por debajo del mínimo, o por encima del máximo.

//+------------------------------------------------------------------+
//| Round a raw lot to the symbol step and clamp to its limits       |
//+------------------------------------------------------------------+
double CRiskEngine::NormalizeLot(double lot)
  {
   double mn = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN);
   double mx = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX);
   double st = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP);
   //--- clamp to the caps first, so the cap itself is snapped to the step below
   if(m_maxLots > 0.0 && lot > m_maxLots)
      lot = m_maxLots;
   if(lot > mx)
      lot = mx;
   //--- floor to the step, then round to the step's own digits, so even a
   //--- non-standard SYMBOL_VOLUME_STEP leaves a clean multiple, not a
   //--- binary remainder like 0.30000000000000004
   if(st > 0.0)
     {
      int vd = (int)MathMax(0.0, MathCeil(-MathLog10(st) - 0.0000001));
      lot = NormalizeDouble(MathFloor(lot / st + 0.0000001) * st, vd);
     }
   if(lot < mn)
      lot = 0.0;   // below the broker minimum: skip the trade, do not round up
   return(lot);
  }

Hay una decisión de criterio en la última línea. Si el tamaño calculado queda por debajo del mínimo del broker, el motor devuelve cero en lugar de redondear hacia arriba. Redondear hacia arriba sería arriesgar más de lo permitido, justo lo contrario de lo que busca la herramienta. Si el riesgo pide menos que el lote mínimo, la respuesta correcta es no operar. Este detalle, que parece menor, va a tener un efecto medible en la prueba. El orden de las operaciones también importa. Primero se aplican los topes, el máximo propio y el del broker, y recién después se ajusta al paso. Así, si el tope recorta el lote, el valor que queda sigue siendo un múltiplo válido del paso y no un número intermedio que el broker rechazaría. El ajuste al paso hace un floor y luego un NormalizeDouble a los dígitos del propio paso, para que un SYMBOL_VOLUME_STEP no estándar no deje un residuo binario como 0.30000000000000004.


El banco de pruebas: un asesor con el modo conmutable

Para probar la herramienta hace falta un asesor que opere de verdad, con una lógica de entrada fija, de modo que lo único que cambie entre ejecuciones sea la forma de calcular el lote. Así, cualquier diferencia en los resultados es del motor de riesgo y no de la estrategia. El asesor de prueba usa una entrada deliberadamente simple, un cruce de dos medias móviles con stop y objetivo por ATR, y una sola posición a la vez. No busca ser una buena estrategia; busca generar las mismas operaciones una y otra vez.

El corazón del asesor es un enumerado con los seis modos y una función que elige el tamaño según el modo activo. Es una sola línea la que cambia de una ejecución a la otra.

//+------------------------------------------------------------------+
//| Money-management modes to compare                                |
//+------------------------------------------------------------------+
enum ENUM_RISK_MODE
  {
   RISK_FIXED_LOT     = 0,   // Fixed lot
   RISK_FIXED_PERCENT = 1,   // Fixed risk percent (base only)
   RISK_STREAK_ONLY   = 2,   // Adaptive: losing streak only
   RISK_VOL_ONLY      = 3,   // Adaptive: volatility only
   RISK_DD_ONLY       = 4,   // Adaptive: drawdown only
   RISK_ADAPTIVE_ALL  = 5    // Adaptive: all factors combined
  };
//+------------------------------------------------------------------+
//| The one switch: same trade, different sizing                     |
//+------------------------------------------------------------------+
double GetLots(const double stopDistance)
  {
   switch(InpRiskMode)
     {
      case RISK_FIXED_LOT:
         return(InpFixedLot);
      case RISK_FIXED_PERCENT:
         return(g_risk.LotsBaseOnly(stopDistance));
      case RISK_STREAK_ONLY:
         return(g_risk.LotsStreakOnly(stopDistance));
      case RISK_VOL_ONLY:
         return(g_risk.LotsVolOnly(stopDistance));
      case RISK_DD_ONLY:
         return(g_risk.LotsDrawdownOnly(stopDistance));
      case RISK_ADAPTIVE_ALL:
         return(g_risk.LotsAll(stopDistance));
     }
   return(0.0);
  }

Con esto, correr la comparación es cambiar el parámetro del modo y volver a probar. El asesor guarda al final de cada ejecución las estadísticas del tester y la curva de capital en un archivo por modo, para que las seis ejecuciones se puedan comparar sin leer seis informes a mano. El asesor de prueba va adjunto, así que cualquiera puede reproducir exactamente lo que sigue.


Metodología de la prueba

Para que los resultados se puedan reproducir y juzgar, estas son las condiciones exactas de la prueba. Todas las ejecuciones usan lo mismo salvo el modo de cálculo del lote.

  • Símbolo: EURUSD. Marco temporal: una hora.
  • Período: del 1 de enero de 2020 al 1 de agosto de 2026.
  • Modelado: barras de un minuto (OHLC). Depósito inicial: diez mil. Apalancamiento: uno a cien. Costos: solo el spread del broker, sin comisión ni deslizamiento agregados.
  • Entrada: cruce de una media rápida de veinte y una lenta de cincuenta. Una posición a la vez.
  • Salida: stop y objetivo a un múltiplo del ATR de catorce, iguales entre sí, de modo que ganar o perder cada operación no dependa del tamaño del lote sino solo del precio.
  • Motor de riesgo: base del dos por ciento, tope de racha en cinco con paso del quince por ciento, umbral de caída en diez por ciento, ATR promediado sobre cincuenta barras.
  • Corridas: seis, una por modo, con todos los demás parámetros idénticos entre sí.

Un punto importante para leer bien lo que viene. La estrategia de entrada no tiene ventaja: un cruce de medias sobre EURUSD, con stop y objetivo simétricos, pierde despacio por los costos, como se espera de una señal sin dirección. Eso es a propósito. Un banco de pruebas neutral es lo que permite ver el efecto del motor de riesgo sin que lo tape una estrategia buena o mala. Lo que medimos acá no es si el sistema gana, sino cómo cada forma de dimensionar el lote moldea el riesgo sobre la misma serie de señales.

Una precisión, para no vender la prueba como más limpia de lo que es. El lote base es un porcentaje del balance vivo de la cuenta, no de un número fijo. Como cada modo pierde una cantidad distinta, sus balances se separan a medida que avanza la prueba, y con ellos la base sobre la que se aplica cada factor. Así que las seis curvas no son un experimento de una sola variable en sentido estricto: comparten la señal, pero la base del dos por ciento se va moviendo distinto en cada una. Eso no invalida la lectura, porque el efecto que buscamos, menos exposición donde hay riesgo, se ve igual de claro, pero conviene tenerlo presente antes de leer las diferencias como si vinieran de un único factor aislado al miligramo.


Resultados: seis formas de dimensionar la misma serie de señales

Las seis ejecuciones parten de la misma serie de 842 señales de entrada, generadas por el mismo cruce de medias sobre los mismos datos. Lo que cambia entre modos es el tamaño de cada lote. Hay un matiz que conviene decir ya: en el modo más conservador, unas pocas operaciones quedan por debajo del lote mínimo del broker y no se abren, un efecto que retomamos más abajo. Salvo ese puñado, se comparan las mismas operaciones con distinto tamaño. Esta es la comparación.

riskengine_table

Las seis formas de dimensionar, sobre la misma serie de 842 señales de EURUSD en una hora. La fila resaltada es el motor completo. La caída máxima en coral marca los casos más agresivos, en verde los más contenidos.

Lo primero que salta es que el porcentaje fijo del dos por ciento, que suena como la opción prudente, produjo una caída del 73,5 por ciento. La razón es la que anticipamos: con un stop ajustado por ATR, el dos por ciento pide un lote medio de 1,38, y ese tamaño sobre una racha mala cava un pozo enorme. El lote fijo de 0,10, en cambio, apenas mueve la cuenta, con una caída del 11 por ciento, pero no porque sea inteligente, sino porque arriesga poquísimo: es un punto de referencia de riesgo bajo, no una comparación pareja.

La comparación justa está entre los modos que parten del mismo dos por ciento. Desde esa base del 73,5 por ciento de caída, la racha sola la baja apenas, a 70,8. La volatilidad sola, casi igual, a 70,2. La caída sola es el factor que más pesa: baja el pico a 61,2. Y los tres juntos llegan a 57,0. Conviene ser preciso con estos porcentajes, porque se comparan valores que ya son porcentajes. La caída no pasó de 73,5 a 57 puntos por una resta directa de veintidós; la reducción es relativa: 57 es un veintidós por ciento más bajo que 73,5, y 61,2 es un diecisiete por ciento más bajo. En puntos de caída, la mejora del motor completo es de unos dieciséis y medio. El lote medio acompaña: baja de 1,38 en la base a 1,07 con el motor completo.

Vale mirar también las cifras crudas, no solo los porcentajes. Sobre el depósito de diez mil, el porcentaje fijo perdió 4.774, con un factor de beneficio de 0,96 y una pérdida media por operación de 5,67. El motor completo perdió 1.765, con factor de beneficio 0,98 y pérdida media de 2,10. Los seis modos comparten el mismo factor de beneficio apenas por debajo de uno, como corresponde a una señal sin ventaja; lo que cambia no es la dirección del resultado sino su magnitud, y esa magnitud la fija el tamaño. El factor de recuperación y el ratio de Sharpe, aunque negativos porque la señal pierde, mejoran en la misma dirección a medida que el tamaño se adapta.


Las curvas de capital

Los números de una tabla se entienden mejor viéndolos como curvas. Estas son las seis, sobre las mismas operaciones.

riskengine_equity

Las seis curvas de capital, misma serie de operaciones, seis formas de dimensionar. El porcentaje fijo del dos por ciento se hunde más hondo; el factor de la caída y el motor completo hacen el descenso más suave. El lote fijo casi no se mueve porque arriesga muy poco.

Las curvas cuentan lo que la tabla resume. Todas suben y bajan juntas, porque son las mismas operaciones, pero la amplitud de cada una depende del tamaño. En la parte final, cuando la señal encadena su peor tramo, las curvas se separan: el porcentaje fijo, la racha y la volatilidad caen casi pegadas hasta lo más bajo, mientras el motor completo y sobre todo el factor de la caída quedan por encima, con un descenso bastante menos violento. El lote fijo, arriba de todo, apenas se inmuta, que es lo que hace un tamaño chico.


La caída de capital, factor por factor

Si hay una sola cosa que esta herramienta busca contener, es la caída de capital, así que vale aislarla.

riskengine_drawdown

La caída máxima de cada modo, todos partiendo del mismo dos por ciento base. Cada factor la baja algo, y el de la caída es el que más pesa por su cuenta.

El orden es claro. Sobre la misma base, la racha y la volatilidad por separado mueven poco la aguja en este período. El factor de la caída, que recorta el tamaño justo cuando la cuenta ya viene cayendo, es el que hace la diferencia grande, y sumarle los otros dos lo lleva un poco más abajo todavía. La conclusión práctica es que, al menos en estos datos, el factor de la caída es el que carga con casi todo el trabajo de contención, y los otros dos aportan en los bordes.


Qué mejoró y qué empeoró: el compromiso honesto

Sería fácil mirar la tabla y decir que el motor es mejor porque bajó la caída y también la pérdida. Pero esa lectura esconde el punto más importante, y hay que decirlo con todas las letras. Acá el motor bajó la pérdida porque la estrategia pierde: achicar el lote sobre una serie perdedora achica lo que se pierde. En una estrategia con ventaja real pasaría lo contrario en la otra dirección: el mismo recorte que suaviza la caída también recorta la ganancia, sobre todo si justo después de una racha de pérdidas viene la operación ganadora fuerte, que el motor va a tomar con el lote reducido.

Ese es el trato que se hace al usar tamaño adaptativo, y no es gratis. Se compra una caída más contenida a cambio de renunciar a parte de la recuperación. A veces vale la pena, cuando lo que importa es sobrevivir a los peores tramos y no que la cuenta salte de vuelta lo más rápido posible. A veces no, cuando la estrategia tiene una ventaja fuerte y sostenida y lo mejor es dejarla correr. El banco de pruebas neutral no puede decidir eso por uno, porque no tiene ganancia que recortar; lo que sí deja claro es el mecanismo, para que la decisión se tome sabiendo qué se gana y qué se resigna.

Hay un efecto más, medible en los datos, que conviene nombrar. El motor completo, en su versión más conservadora, a veces calculó un lote por debajo del mínimo del broker y salteó la operación. Es decir, el tamaño adaptativo no solo achica: en el extremo, reduce la cantidad de operaciones. Eso baja la exposición, pero también significa menos actividad, otro costado del mismo trato. El asesor de prueba lleva la cuenta exacta de esas operaciones salteadas, para quien quiera medirlo en su propia ejecución.


El riesgo de sobreoptimizar los parámetros

Este motor tiene parámetros, el paso y el tope de la racha, el umbral de la caída, las barras del promedio del ATR, y cualquier cosa con parámetros se puede ajustar hasta que la historia se vea linda. Es la trampa más vieja del testeo, y hay que decirla claro. Que en estos datos el factor de la caída haya sido el que más la bajó no quiere decir que vaya a ser así siempre, ni en otro símbolo, ni en otro período. Es un resultado sobre una historia particular.

Antes de confiar en un juego de parámetros conviene tres cosas. Probarlos fuera de muestra, en un tramo de historia que no se usó para elegirlos, porque un ajuste que solo brilla en el período donde se lo calibró no vale nada. Probarlos en varios símbolos, porque un valor que contiene la caída en EURUSD puede sobre-recortar en oro. Y tener presente que bajar la caída no es lo mismo que mejorar la rentabilidad: muchas veces el tamaño adaptativo simplemente reduce la actividad y la exposición, y con ellas baja el riesgo y la ganancia a la vez. La herramienta contiene el riesgo; que eso convenga o no depende de la estrategia sobre la que se monta, y eso hay que medirlo en cada caso, no darlo por sentado.


Cómo enchufarlo a un asesor real

Más allá del banco de pruebas, así se conecta el motor a un asesor de verdad, con el camino mínimo completo, de la inicialización al envío de la orden. En la inicialización, una vez creado el handle de ATR más arriba, se crea el objeto con el riesgo base y ese handle, y se ajustan los parámetros de cada factor.

   if(!g_risk.Init(_Symbol, InpRiskPct, g_atr))
      return(INIT_FAILED);
   g_risk.SetStreak(InpStreakCap, InpStreakStep);
   g_risk.SetDrawdown(InpDDthresh);
   g_risk.SetMaxLots(InpMaxLots);

Y en el momento de abrir, ya con la señal del cruce en la variable `sig`, se calcula la distancia del stop, se pide el lote para esa distancia, y se verifica que llegue al mínimo del broker antes de mandar la orden. Si el motor devolvió un tamaño por debajo del mínimo, la operación se saltea en lugar de enviar una orden condenada.

   double stopDist = InpSLmult * atr[0];
   double tpDist   = InpTPmult * atr[0];
   double lots     = GetLots(stopDist);
   double minLot   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   if(lots < minLot)                // adaptive sizing wanted less than the broker allows
     {
      g_skipped++;                  // count the skipped trade instead of sending a doomed order
      return;
     }
   if(lots <= minLot)
      g_atMin++;                    // taken, but the sizing was clamped at the floor

   MqlTick tk;
   if(!SymbolInfoTick(_Symbol, tk))
      return;
   int    dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   if(sig > 0)                      // sig > 0: buy
     {
      double sl = NormalizeDouble(tk.ask - stopDist, dig);
      double tp = NormalizeDouble(tk.ask + tpDist, dig);
      if(g_trade.Buy(lots, _Symbol, tk.ask, sl, tp))
        { g_lotSum += lots; g_lotCount++; }
     }

La variable `sig` viene de la función de señal del asesor, un cruce de medias que devuelve uno para comprar, menos uno para vender y cero cuando no hay cruce. El asesor de prueba usa `GetLots`, que enruta al método del motor según el modo elegido, y el contador `g_skipped` es solo para el reporte del banco de pruebas. En un asesor propio se llama directo al método que se quiera, por ejemplo `g_risk.LotsAll(stopDist)` para el motor completo, o cualquiera de los parciales para un solo factor. Cambiar ese método es todo lo que hace falta para pasar de un modo a otro.


Limitaciones

  1. El motor recorta el tamaño, nunca lo agranda. Es una herramienta de defensa, no una forma de apostar más fuerte cuando las cosas van bien.
  2. La estrategia de la prueba es un banco neutral sin ventaja, elegido para aislar el efecto del tamaño. Sobre una estrategia con ventaja real los números serían otros, pero el trato de fondo, menos caída a cambio de menos recuperación, se mantiene.
  3. Los resultados son de un símbolo y un período. Antes de confiar en un juego de parámetros hay que probarlo fuera de muestra y en varios símbolos.
  4. Los tres factores se multiplican. En un escenario muy malo, con racha, volatilidad alta y caída a la vez, los recortes se acumulan y el lote puede quedar en el piso o por debajo del mínimo, salteando la operación.
  5. La volatilidad entra dos veces. El stop se fija con el ATR, y un stop más ancho ya reduce el lote base, porque el mismo riesgo en dinero pide menos lotes cuando la distancia crece. El factor de volatilidad vuelve a recortar cuando el ATR sube. Ese doble efecto es parte de por qué la volatilidad sola mueve tan poco la aguja en esta prueba.
  6. La prueba cobra solo el spread del broker, sin comisión ni deslizamiento. Sumarlos ahondaría todas las curvas, y más las de lote grande, así que el efecto no cambiaría de signo, pero los números exactos sí.
  7. El backtest modela cada barra con datos OHLC de un minuto, no con ticks reales. Los toques de stop y objetivo dentro de la barra son aproximados, y con datos de tick el resultado exacto de cada operación podría moverse.
  8. Motor de gestión de riesgo, no de generación de señales. No decide cuándo entrar, solo con cuánto.


Conclusión

El tamaño de la posición es lo que decide si una racha mala es un bache o un pozo, y sin embargo se suele resolver con un número fijo que ignora todo lo que pasa alrededor. La prueba mostró algo incómodo: hasta el porcentaje fijo del dos por ciento, que casi todos damos por prudente, puede sobredimensionar el lote con un stop ajustado y cavar una caída del setenta por ciento. Los tres factores de contexto, y sobre todo el de la caída, buscan reducir la agresividad de la exposición en los tramos adversos, y en estos datos bajaron la caída máxima del 73 al 57 por ciento.

Pero el motor no regala nada. Contiene el riesgo a cambio de resignar parte de la recuperación, y esa es la decisión que hay que tomar con los ojos abiertos, midiendo sobre la estrategia propia, fuera de muestra y en varios símbolos. La herramienta que queda es una clase que se enchufa a cualquier asesor y un banco de pruebas para medirla, no una promesa de ganar más. Los parámetros son un punto de partida para ajustar y validar en cada estrategia.


Archivos e instalación

El proyecto son dos archivos, que van juntos en una subcarpeta dentro de MQL5\Experts.

Archivo Qué contiene
RiskEngine.mqh La clase CRiskEngine, con un método por modo de cálculo del lote.
RiskModeTest.mq5 El asesor de prueba: cruce de medias con stop por ATR y el modo de riesgo conmutable, que guarda las estadísticas y la curva de cada ejecución.

Los pasos para reproducir la prueba:

  1. Descomprime el adjunto en la carpeta MQL5\Experts, de modo que los dos archivos queden juntos en una subcarpeta, y compila el asesor.
  2. Abre el simulador de estrategias, elige el asesor, el símbolo, el marco temporal y el período, con el modelado de un minuto.
  3. Corre una vez por cada valor del modo de riesgo. Cada ejecución escribe sus estadísticas y su curva de capital en la carpeta de archivos común, un archivo por modo, para comparar.
Archivos adjuntos |
RiskEngine.mqh (12.86 KB)
RiskModeTest.mq5 (12.82 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Creación de un sistema de trading (Parte 2): La ciencia del dimensionamiento de posiciones Creación de un sistema de trading (Parte 2): La ciencia del dimensionamiento de posiciones
Incluso con un sistema de expectativas matemáticas positivas, el tamaño de la posición determina si uno prospera o fracasa. Es el pilar de la gestión de riesgos: traducir las ventajas estadísticas en resultados reales, al tiempo que se protege el capital.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Red neuronal en la práctica: Lectura de PNG del MNIST Red neuronal en la práctica: Lectura de PNG del MNIST
Aquí veremos cómo implementar un algoritmo sencillo para leer imágenes en formato PNG. Poder leer el contenido de estas imágenes será importante para avanzar en el desarrollo de una topología de red perceptrón. Esto se debe a que comenzaremos a utilizar la base de datos MNIST para entrenar la red que implementaremos.