English Русский 中文 Deutsch 日本語 Português
preview
Asesor Experto Grid-Hedge Modificado en MQL5 (Parte I): Creamos un sencillo asesor de cobertura

Asesor Experto Grid-Hedge Modificado en MQL5 (Parte I): Creamos un sencillo asesor de cobertura

MetaTrader 5Sistemas comerciales | 12 junio 2024, 16:28
436 0
Kailash Bai Mina
Kailash Bai Mina

Introducción

Quiere sumergirse en el mundo del trading con asesores, pero no deja de toparse con la frase: ¿"No se utiliza cobertura/rejilla/martingale peligrosos"? Quizá se pregunte qué tienen de malo estas estrategias. ¿Por qué la gente sigue diciendo que son arriesgadas y qué hay realmente detrás de estas afirmaciones? Puede que incluso esté pensando: "¿Podemos cambiar estas estrategias para hacerlas más seguras?". ¿Por qué se molestan los tráders en utilizar estas estrategias? ¿Cuáles son sus aspectos positivos y negativos? Si estos pensamientos se le han pasado por la cabeza, está usted en el lugar adecuado.

Empezaremos creando un sencillo asesor de cobertura. Este será el primer paso de un proyecto más amplio, el asesor Grid-Hedge, que supondrá una mezcla de rejilla clásica y estrategias de cobertura Al final de este artículo, sabrá cómo desarrollar una estrategia de cobertura básica y tendrá una idea de la rentabilidad de dicha estrategia.

Pero no nos detendremos ahí. En esta serie analizaremos desde dentro las estrategias mencionadas. Nuestro objetivo es ver si podemos modificar estas estrategias tradicionales y usarlas para obtener beneficios sólidos con el trading automatizado.

Aquí tenemos una descripción general de lo que abarcaremos en el presente artículo:

  1. Cobertura clásica
  2. Automatización de una estrategia de cobertura clásica
  3. Comprobación histórica de una estrategia de cobertura clásica
  4. Conclusión


Cobertura clásica

En primer lugar, deberíamos hablar sobre la estrategia.

Primero abriremos una posición de compra, digamos en el nivel de precios 1000 y colocaremos un stop loss en 950 y un take profit en 1050. Es decir, si alcanzamos el stop loss, perderemos 50 USD, y si alcanzamos el take profit, ganaremos 50 USD. Una vez alcanzado el take profit, fijaremos el beneficio y se acabó. Si se activa el stop loss, perderemos 50 USD. Vamos ahora a colocar una posición de venta en 950, fijando el take profit en 900 y el stop loss en 1000. Si esta nueva posición de venta alcanza el take profit, obtendremos 50 USD. Como ya hemos perdido 50 USD, nuestro beneficio neto será de 0 USD, y en caso de que alcance el stop loss, es decir, el nivel de precios de 1000, volveremos a perder 50 USD, por lo que nuestra pérdida total será de 100 USD, pero ahora en este punto volveremos a colocar una posición de compra con el mismo take profit y stop loss que la posición de compra anterior. Si esta nueva posición de compra alcanza el take profit, obtendremos 50 USD y nuestro beneficio neto total será de -50-50+50 = -50 USD, es decir, una pérdida de 50 USD. Si se alcanza el stop loss, nuestra pérdida total será de -50-50-50=-150 USD.

Para simplificar, por ahora ignoraremos los spreads y las comisiones.

Usted podría pensar: "¿Cómo se puede ganar dinero en una situación así?". La respuesta está en el tamaño del lote. ¿Y si aumentamos el tamaño del lote de cada posición posterior? Regresemos a nuestra estrategia.

Vamos a abrir una posición de compra de 0,01 lotes (mínimo posible) a un precio de 1000:

  • si alcanzamos el take profit (1050), cerraremos con un beneficio de 50 USD.
  • si alcanzamos el stop loss (950), perderemos 50 USD.

Según nuestra estrategia, en este caso colocaremos inmediatamente una posición de venta de 0,02 lotes (el doble) a 950:

  • Si alcanzamos el take profit (900), tendremos un beneficio neto total de -50+100 = 50 USD.
  • si alcanzamos el stop loss (1000), perderemos un total de 50+100 = 150 USD.

En este caso, colocaremos inmediatamente una posición de compra de 0,04 lotes (de nuevo duplicaremos) a un precio de 1000:

  • si alcanzamos el take profit (1050), obtendremos un beneficio neto total de -50-100+200 = 50 USD.
  • si se activa el stop loss (950), perderemos un total de 50+100+150 = 350 USD.

En este caso, colocaremos inmediatamente una posición de venta de 0,08 lotes (de nuevo duplicamos) a 950:

  • Si alcanzamos el take profit (900), tendremos un beneficio neto total de -50-100-150+400 = 50 USD.
  • si alcanzamos el stop loss (1000), perderemos un total de 50+100+150+200 = 500 USD.

... 

Como habrá podido comprobar, en cualquier caso obtendremos un beneficio de 50 USD. De lo contrario, la estrategia continúa. La estrategia se mantendrá hasta que alcancemos el take profit en 900 o 1050; el precio alcanzará finalmente cualquiera de estos dos puntos y seguramente obtendremos un beneficio garantizado de 50 USD.

En el caso anterior, hemos colocado primero una posición de compra, pero no será necesario empezar por ahí. Alternativamente, podemos iniciar la estrategia vendiendo 0,01 lotes (en nuestro caso).

Esta opción de inicio del comercio es muy importante porque luego modificaremos la estrategia para conseguir más flexibilidad. Por ejemplo, puede que necesitemos definir un punto de entrada (en nuestro caso, una compra inicial) para el ciclo anterior, pero sería problemático limitar este punto de entrada únicamente a una posición de compra, ya que podríamos definir el punto de entrada de tal forma que fuera ventajoso colocar inicialmente una posición de venta.

Una estrategia que comience desde una posición de venta resultará absolutamente simétrica al caso anterior, cuando se empieza a comerciar desde una posición de compra. Nuestra estrategia sería algo así:

  • si alcanzamos el take profit (900), nos aseguraremos un beneficio de 50 USD.
  • si alcanzamos el stop loss (1000), perderemos 50 USD.

En este caso, colocaremos inmediatamente una posición de compra de 0,02 lotes (el doble) a un precio de 1000:

  • Si alcanzamos el take profit (1050), tendremos un beneficio neto total de -50+100 = 50 USD.
  • si alcanzamos el stop loss (950), perderemos un total de 50+100 = 150 USD.

Según nuestra estrategia, en este caso colocaremos inmediatamente una posición de venta de 0,04 lotes (el doble) a 950:

  • si alcanzamos el take profit (900), obtendremos un beneficio neto total de -50-100+200 = 50 USD.
  • si alcanzamos el stop loss (1000), perderemos un total de 50+100+150 = 350 USD.

... y así sucesivamente.

Una vez más, como podemos ver, esta estrategia solo terminará cuando alcancemos el nivel de precio de 900 o 1050, lo que con seguridad resultará en un beneficio de $50. Si no alcanzamos estos niveles de precios, la estrategia proseguirá hasta que finalmente los alcancemos.

Nota: No es necesario multiplicar por 2 el tamaño del lote. Podemos aumentarlo con cualquier multiplicador, si bien cualquier multiplicador inferior a 2 no garantizará un beneficio en ninguno de los casos anteriores. Hemos seleccionado 2 para simplificar y podríamos cambiar este valor cuando optimicemos más la estrategia.

Con esto concluimos nuestro análisis de la estrategia de cobertura clásica.


Automatización de una estrategia de cobertura clásica

En primer lugar, tendremos que debatir un plan para crear el asesor. El desarrollo puede hacerse de varias formas. Consideremos dos enfoques básicos:

  • Primero: La definición de cuatro niveles (variables) como se indica en la estrategia y la colocación de posiciones siempre que el precio vuelva a cruzar estas líneas, como se indica en nuestra estrategia.
  • Segundo: Uso de órdenes pendientes. Al ejecutar una orden pendiente, se colocarán las siguientes.

    Ambos enfoques resultan muy similares y se puede discutir durante mucho tiempo sobre cuál es mejor. Nos centraremos más en el primero, ya que es más fácil de desarrollar y comprender.


    Automatización de una estrategia de cobertura clásica

    En primer lugar, declararemos algunas variables en el espacio global:

    input bool initialPositionBuy = true;
    input double buyTP = 15;
    input double sellTP = 15;
    input double buySellDiff = 15;
    input double initialLotSize = 0.01;
    input double lotSizeMultiplier = 2;

    1. isPositionBuy - variable lógica que determina qué tipo de posición (compra o venta) se colocará a continuación. Si es cierto la siguiente será una compra, de lo contrario será una venta.
    2. buyTP - distancia entre A y B, que supondrá el take profit de las posiciones de compra (en pips). A y B se determinarán más adelante.
    3. sellTP - distancia entre C y D, que supondrá el take profit de las posiciones de venta (en pips). C y D se determinarán más adelante.
    4. buySellDiff - distancia entre B y C, que supondrá el nivel de precio de compra y el nivel de precio de venta (en pips).
    5. intialLotSize - tamaño del lote de la primera posición.
    6. lotSizeMultiplier - multiplicador del tamaño del lote para la siguiente posición.
    A, B, C, D suponen esencialmente niveles de precios en orden descendente de arriba a abajo.

    Nota: Las variables se usarán posteriormente para optimizar la estrategia.

    Por ejemplo, hemos establecido unos buyTP, SellTP y buySellDiff iguales a 15 pips, pero los cambiaremos más tarde y veremos qué valores nos darán el beneficio y la reducción óptimos.

    Estas son las variables de entrada que se usarán posteriormente para la optimización.

    Ahora crearemos algunas variables más en el espacio global:

    double A, B, C, D;
    bool isPositionBuy;
    bool hedgeCycleRunning = false;
    double lastPositionLotSize;

    1. Primero definiremos cuatro niveles denominados A, B, C, D como variables duales:
      • A: Nivel de take profit para todas las posiciones de compra.
      • B: Precio de apertura para todas las posiciones de compra y stop-loss para todas las posiciones de venta.
      • C: Precio de apertura para todas las posiciones de venta y stop-loss para todas las posiciones de compra.
      • D: Nivel de take profit para todas las posiciones de venta.
    2. isPositionBuy - variable lógica. Si es igual a true, la posición inicial será de compra, si es falso, será de venta.
    3. hedgeCycleRunning - variable lógica. True significará que se está ejecutando un ciclo de cobertura, es decir, se ha abierto la orden inicial pero aún no se han alcanzado los niveles de precio A o D que definimos anteriormente, y false significará que el nivel de precio ha alcanzado A o D y entonces se iniciará un nuevo ciclo, que veremos más adelante. Además, esta variable será falsa por defecto.
    4. lastPositionLotSize - como su nombre indica, esta variable de tipo double contendrá siempre el tamaño de lote de la última orden abierta y, si no se ha iniciado el ciclo, tomará un valor igual a la variable de entrada initialLotSize, que fijaremos más adelante.

    Vamos a crear una función: 
    //+------------------------------------------------------------------+
    //| Hedge Cycle Intialization Function                               |
    //+------------------------------------------------------------------+
    void StartHedgeCycle()
       {
        isPositionBuy = initialPositionBuy;
        double initialPrice = isPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
        A = isPositionBuy ? initialPrice + buyTP * _Point * 10 : initialPrice + (buySellDiff + buyTP) * _Point * 10;
        B = isPositionBuy ? initialPrice : initialPrice + buySellDiff * _Point * 10;
        C = isPositionBuy ? initialPrice - buySellDiff * _Point * 10 : initialPrice;
        D = isPositionBuy ? initialPrice - (buySellDiff + sellTP) * _Point * 10 : initialPrice - sellTP * _Point * 10;
    
        ObjectCreate(0, "A", OBJ_HLINE, 0, 0, A);
        ObjectSetInteger(0, "A", OBJPROP_COLOR, clrGreen);
        ObjectCreate(0, "B", OBJ_HLINE, 0, 0, B);
        ObjectSetInteger(0, "B", OBJPROP_COLOR, clrGreen);
        ObjectCreate(0, "C", OBJ_HLINE, 0, 0, C);
        ObjectSetInteger(0, "C", OBJPROP_COLOR, clrGreen);
        ObjectCreate(0, "D", OBJ_HLINE, 0, 0, D);
        ObjectSetInteger(0, "D", OBJPROP_COLOR, clrGreen);
    
        ENUM_ORDER_TYPE positionType = isPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
        double SL = isPositionBuy ? C : B;
        double TP = isPositionBuy ? A : D;
        CTrade trade;
        trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, SL, TP);
        
        lastPositionLotSize = initialLotSize;
        if(trade.ResultRetcode() == 10009) hedgeCycleRunning = true;
        isPositionBuy = isPositionBuy ? false : true;
       }
    //+------------------------------------------------------------------+

    El tipo de función es void, lo cual significa que no necesitaremos que retorne nada. La función opera del siguiente modo:

    En primer lugar, estableceremos la variable isPositionBuy (bool) igual a la variable de entrada InitialPositionBuy, que nos indicará qué tipo de posición colocar al principio de cada ciclo. Puede que se pregunte por qué necesitamos dos variables si ambas son iguales, pero tenga en cuenta que cambiaremos isPositionBuy alternativamente (la última línea del bloque de código anterior). Sin embargo, el parámetro InitialPositionBuy siempre será fijo y no lo modificaremos.

    A continuación definiremos la nueva variable (de tipo double) InitialPrice, que estableceremos como igual a Ask o Bid mediante un operador ternario. Si isPositionBuy es igual a true, InitialPrice pasará a ser igual al precio Ask en ese momento y al precio Bid en caso contrario.

    A continuación, definiremos las variables (de tipo doble) que hemos discutido brevemente antes, es decir, las variables A, B, C, D, utilizando el operador ternario de la siguiente manera:

    1. Si isPositionBuy es True:
      • A será igual a la suma de initialPrice y buyTP (variable de entrada), donde buyTP se multiplicará por un factor (_Point*10) y _Point será la función predefinida Point().
      • B será igual a initialPrice
      • C será igual a initialPrice menos buySellDiff (variable de entrada), donde  buySellDiff se multiplicará por el coeficiente (_Point*10).
      • D será igual a initialPrice menos la suma de buySellDiff y sellTP, que se multiplicará por un factor de (_Point*10).

    2. Si isPositionBuy es False:
      • A será igual a la suma de initialPrice y (buySellDiff + buyTP), que se multiplicará por el factor (_Point*10).
      • B es igual a la suma de initialPrice y buySellDiff, donde buySellDiff se multiplicará por el factor (_Point*10).
      • C será igual a to initialPrice
      • D será igual a initialPrice menos sellTP, donde sellTP se multiplicará por el factor (_Point*10).

    Ahora para la visualización dibujaremos algunas líneas en el gráfico representando los niveles de precio A, B, C, D usando ObjectCreate y estableceremos la propiedad color en clrGreen usando ObjectSetInteger (el color puede ser cualquiera).

    Ahora necesitaremos abrir una orden inicial de compra o venta dependiendo de la variable isPositionBuy. Para ello, definiremos tres variables: positionType, SL, TP.

    1. positionType: El tipo de esta variable ENUM_ORDER _TYPE es un tipo de variable de usuario predefinido que puede tomar valores enteros de 0 a 8 según la siguiente tabla:

      Valores enteros Identificador
       0 ORDER_TYPE_BUY
       1 ORDER_TYPE_SELL
       2 ORDER_TYPE_BUY_LIMIT
       3 ORDER_TYPE_SELL_LIMIT
       4 ORDER_TYPE_BUY_STOP
       5 ORDER_TYPE_SELL_STOP
       6 ORDER_TYPE_BUY_STOP_LIMIT
       7 ORDER_TYPE_SELL_STOP_LIMIT
       8 ORDER_TYPE_CLOSE_BY

      Como podemos ver, el 0 representa ORDER_TYPE_BUY, mientras que el 1 representa ORDER_TYPE_SELL. Solo necesitaremos estos dos valores. Usaremos un identificador en lugar de valores enteros porque son difíciles de recordar.

    2. SL: si isPositionBuy es igual a true, SL será igual al nivel de precios C, de lo contrario será B

    3. TP: si isPositionBuy es igual a true, TP será igual al nivel de precios A, de lo contrario será igual al nivel de precios D

    Utilizando estas tres variables, tendremos que colocar la posición de la siguiente manera:

    En primer lugar, importaremos la biblioteca comercial estándar utilizando #include:

    #include <Trade/Trade.mqh>
    

    Justo antes de abrir una posición, crearemos un ejemplar de la clase CTrade utilizando:

    CTrade trade;
    
    trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, SL, TP);

    Usando este ejemplar, colocaremos una posición con la función PositionOpen en este ejemplar con los siguientes parámetros:

    1. _Symbol especificará el símbolo actual al que está vinculado el asesor.
    2. positionType - variable ENUM_ORDER_TYPE que hemos definido antes.
    3. initialLotSize - tamaño inicial del lote.
    4. initialPrice - precio de apertura de la orden, que será igual a Ask (para posiciones de compra) o Bid (para posiciones de venta).
    5. Por último, proporcionaremos los niveles de precios SL y TP.

    Esto colocará una posición de compra o venta. Ahora, después de colocar una posición, almacenaremos su tamaño de lote en una variable definida globalmente llamada LastPositionLotSize para que podamos utilizar ese lote y el multiplicador de los datos de entrada para calcular el tamaño de lote de la siguiente posición.

    Tenemos dos cosas más que hacer:

    if(trade.ResultRetcode() == 10009) hedgeCycleRunning = true;
    isPositionBuy = isPositionBuy ? false : true;

    Aquí estableceremos hedgeCycleRunning en true solo si la posición se coloca con éxito. Esto viene determinado por la función ResultRetcode() del ejemplar de CTrade denominado trade, que devuelve "10009", lo cual indica una colocación correcta (puede ver todos estos códigos de retorno aquí). La razón para utilizar hedgeCycleRunning se revelará en código posterior.

    Por último, utilizaremos un operador ternario para cambiar el valor de isPositionBuy. Si era igual a falso, se convertirá en true, y viceversa. Hacemos esto porque nuestra estrategia establece que una vez que se abra la posición inicial, la venta se colocará después de la compra y la compra se colocará después de la venta, lo cual significa que se alternan.

    Con esto concluimos la discusión de nuestra función StartHedgeCycle(), de importancia fundamental. La usaremos mucho.

    Veamos ahora el último fragmento de código.

    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
       {
        double _Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double _Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    
        if(!hedgeCycleRunning)
           {
            StartHedgeCycle();
           }
    
        if(_Bid <= C && !isPositionBuy)
           {
            double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
            trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, newPositionLotSize, _Bid, B, D);
            lastPositionLotSize = newPositionLotSize;
            isPositionBuy = isPositionBuy ? false : true;
           }
        
        if(_Ask >= B && isPositionBuy)
           {
            double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
            trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, _Ask, C, A);
            lastPositionLotSize = newPositionLotSize;
            isPositionBuy = isPositionBuy ? false : true;
           }
        
        if(_Bid >= A || _Ask <= D)
           {
            hedgeCycleRunning = false;
           }
       }
    //+------------------------------------------------------------------+

    Las dos primeras líneas hablan por sí mismas, simplemente definen _Ask y _Bid (variables dobles) que almacenan Ask y Bid en un momento dado.

    A continuación, utilizaremos la sentencia if para iniciar el ciclo de cobertura utilizando StartHedgeCycle() si la variable hedgeCycleRunning tiene el valor false. Ya sabemos lo que hace StartHedgeCycle(), pero vamos a resumirlo. Esta función:

    1. Define los niveles de precio A, B, C, D.
    2. Dibuja líneas verdes horizontales en los niveles de precio A, B, C, D para su visualización.
    3. Abre una posición.
    4. Almacena el tamaño del lote de una posición en una variable LastPositionLotSize definida en el espacio global para que pueda utilizarse en todas partes.
    5. Establece hedgeCycleRunning en true. Por eso ejecutaremos la función StartHedgeCycle().
    6. Por último, cambia los valores de la variable isPositionBuy de true a false y de false a true tal y como se indica en nuestra estrategia.

    Ejecutaremos StartHedgeCycle() solo una vez, cuando hedgeCycleRunning sea false, y lo cambiaremos a false al final de la función. A menos que establezcamos hedgeCycleRunning a false de nuevo, StartHedgeCycle() no se ejecutará nuevamente.

    Por ahora nos saltaremos las dos siguientes sentencias if; volveremos a ellas más tarde. Echemos un vistazo a la última sentencia if:

    if(_Bid >= A || _Ask <= D)
       {
        hedgeCycleRunning = false;
       }

    Esta garantiza el reinicio del ciclo. Como hemos comentado antes, si establecemos hedgeCycleRunning en true, el ciclo se reiniciará y todo lo que hemos comentado antes sucederá nuevamente. Además, nos hemos asegurado de que cuando se reinicie un ciclo, todas las posiciones del ciclo anterior se cierren con un Take Profit (ya sea una posición de Compra o de Venta).

    Así pues, hemos procesado el inicio del ciclo, su final y su reinicio, pero aún nos falta la parte principal, es decir, el procesamiento de la apertura de una orden cuando el precio alcanza el nivel B desde abajo o el nivel C desde arriba. El tipo de posición también deberá ser alternativo: la compra se abrirá solo en el nivel B, mientras que la venta solo se abrirá en el nivel C.

    Nos hemos saltado el código que procesa este comportamiento, así que volveremos a él.

    if(_Bid <= C && !isPositionBuy)
       {
        double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
        CTrade trade;
        trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, newPositionLotSize, _Bid, B, D);
        lastPositionLotSize = lastPositionLotSize * lotSizeMultiplier;
        isPositionBuy = isPositionBuy ? false : true;
       }
    
    if(_Ask >= B && isPositionBuy)
       {
        double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
        CTrade trade;
        trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, _Ask, C, A);
        lastPositionLotSize = lastPositionLotSize * lotSizeMultiplier;
        isPositionBuy = isPositionBuy ? false : true;
       }

     Así, estas dos sentencias if gestionarán la apertura de una orden entre el ciclo (entre la colocación de la posición inicial y antes del final del ciclo).

    1. Primera sentencia IF: Controla la apertura de una orden de venta. Si la variable Bid (que contiene el precio Bid en este momento) se encuentra por debajo o es igual al nivel C y isPositionBuy es false, entonces definiremos una variable double llamada newPositionLotSize. La establceremos como LastPositionLotSize multiplicado por lotSizeMultiplier, y luego el valor double se normalizará a dos decimales utilizando una función predefinida llamada NormalizeDouble.

      A continuación, colocaremos una posición para la venta utilizando la función predefinida PositionOpen() desde una ejemplar de CTrade llamado trade, transmitiendo newPositionLotSize como parámetro. Finalmente, estableceremos para LastPositionLotSize este nuevo tamaño de lote (sin normalizar) para poder multiplicarlo en posiciones posteriores, y por último, alternaremos el valor de isPositionBuy de true a false o de false a true. 

    2. Segundo operador IF: controla la apertura de una orden de compra. Si el precio Ask (la variable que contiene el precio Ask en este momento) es igual o mayor que el nivel B y isPositionBuy es true, definiremos una variable double llamada newPositionLotSize. Estableceremos newPositionLotSize igual a LastPositionLotSize multiplicado por lotSizeMultiplier, y normalizaremos el valor double a dos decimales utilizando la función predefinida NormalizeDouble como antes.

      A continuación, colocaremos una posición de compra utilizando la función predefinida PositionOpen() desde el ejemplar CTrade llamado trade, transmitiendo newPositionLotSize como parámetro. Por último, fijaremos LastPositionLotSize en este nuevo tamaño de lote (sin normalizar) para poder multiplicarlo en posiciones posteriores. Por último, alternaremos los valores de isPositionBuy de true a false o de false a true.

    Hay dos puntos muy importantes a considerar aquí:

    • En la primera sentencia IF, utilizaremos el precio Bid y especificaremos que se debe abrir una posición cuando Bid caiga por debajo o sea igual a C, mientras que isBuyPosition se establecerá en false. ¿Por qué utilizamos Bid aquí?

      Supongamos que utilizamos el precio Ask, entonces existirá la posibilidad de que se cierre la posición de compra anterior, pero no se abra una nueva posición de venta. Sabemos que la compra se abre en Ask y se cierra en Bid, dejando la posibilidad de que la compra se cierre cuando Bid cruce o iguale la anterior línea de nivel de precio C. Esto cerrará la orden de compra con el stop loss que establecimos anteriormente al abrir la posición, pero la orden de venta no se abrirá todavía. Por lo tanto, si los precios de compra y venta ascienden, nuestra estrategia no se llevará a cabo. Por este motivo, utilizaremos Bid en lugar de Ask.

      De forma simétrica, en el segundo operador IF, utilizaremos el precio Ask y declararemos que abriremos una posición cuando Ask suba por encima o sea igual a B y isBuyPosition sea true. ¿Por qué usamos Ask aquí?

      Supongamos que utilizamos el precio Bid, entonces existirá la posibilidad de que se cierre la posición de venta anterior, pero no se abrirá una nueva posición de compra. Sabemos que la venta se abre al precio Bid y se cierra al precio Ask, lo cual deja la posibilidad de que la venta se cierre cuando Ask cruce o iguale la línea de nivel de precio B desde abajo, cerrando así la orden de venta en el stop loss que fijamos anteriormente al abrir la posición. No obstante, aún no se ha abierto una orden de compra. Por consiguiente, si los precios de compra y venta caen, nuestra estrategia no se llevará a cabo. Por eso usaremos Ask en lugar de Bid.

      Así, la cuestión consiste en que si se cierra una posición de compra/venta, para seguir la estrategia correctamente, deberemos abrir inmediatamente una posición de venta/compra respectivamente.

    • En ambas sentencias IF, mencionamos que cuando establecemos LastPositionLotSize, lo igualaremos a (lastPositionLotSize * lotSizeMultiplier) en lugar de newPositionLotSize, que es igual al valor normalizado de (lastPositionLotSize * lotSizeMultiplier) a dos decimales usando la función predefinida NormalizeDouble().
      NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2)
      ¿Por qué hemos hecho esto? Cuando se equipare a un valor normalizado, nuestra estrategia se seguirá correctamente. Por ejemplo, supongamos que fijamos un tamaño de lote inicial de 0,01 y un multiplicador de 1,5, entonces el primer tamaño de lote será, obviamente, 0,01 y el siguiente 0,01. *1,5 = 0,015. Por supuesto, no podemos abrir un tamaño de lote de 0,015. Esto no está autorizado por el bróker. El multiplicador deberá ser un múltiplo entero de 0,01. Por eso hemos normalizado el tamaño del lote a 2 decimales. Así, se abrirá 0,01. Ahora tendremos 2 opciones para establecer el valor de lastPositionLotSize: 0,01 (0,010) o 0,015. Supongamos que seleccionamos 0,01 (0,010), entonces la próxima vez que coloquemos una posición utilizaremos 0,01 * 1,5 = 0,015. Tras la normalización, se convertirá en 0,01 y así sucesivamente. Así que usamos un multiplicador de 1,5 y empezamos con un tamaño de lote de 0,01, pero el tamaño de lote nunca aumentó y nos quedamos atascados en un ciclo. Todas las posiciones se colocan con un tamaño de lote de 0,01, lo cual significa que no debemos equiparar LastPositionLotSize a un valor de 0,01 (0,010), por lo que en su lugar elegiremos la opción 0,015, que es el valor anterior a la normalización.

      Precisamente por eso establecemos un valor lastPositionLotSize igual a (lastPositionLotSize * lotSizeMultiplier), y no NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2).

    Finalmente, todo nuestro código tendrá este aspecto:

    #include <Trade/Trade.mqh>
    
    input bool initialPositionBuy = true;
    input double buyTP = 15;
    input double sellTP = 15;
    input double buySellDiff = 15;
    input double initialLotSize = 0.01;
    input double lotSizeMultiplier = 2;
    
    
    
    double A, B, C, D;
    bool isPositionBuy;
    bool hedgeCycleRunning = false;
    double lastPositionLotSize;
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
       {
        return(INIT_SUCCEEDED);
       }
    
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
       {
        ObjectDelete(0, "A");
        ObjectDelete(0, "B");
        ObjectDelete(0, "C");
        ObjectDelete(0, "D");
       }
    
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
       {
        double _Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double _Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    
        if(!hedgeCycleRunning)
           {
            StartHedgeCycle();
           }
    
        if(_Bid <= C && !isPositionBuy)
           {
            double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
            CTrade trade;
            trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, newPositionLotSize, _Bid, B, D);
            lastPositionLotSize = lastPositionLotSize * lotSizeMultiplier;
            isPositionBuy = isPositionBuy ? false : true;
           }
        
        if(_Ask >= B && isPositionBuy)
           {
            double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
            CTrade trade;
            trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, _Ask, C, A);
            lastPositionLotSize = lastPositionLotSize * lotSizeMultiplier;
            isPositionBuy = isPositionBuy ? false : true;
           }
        
    if(_Bid >= A || _Ask <= D)
       {
        hedgeCycleRunning = false;
       }
       }
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //| Hedge Cycle Intialization Function                               |
    //+------------------------------------------------------------------+
    void StartHedgeCycle()
       {
        isPositionBuy = initialPositionBuy;
        double initialPrice = isPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
        A = isPositionBuy ? initialPrice + buyTP * _Point * 10 : initialPrice + (buySellDiff + buyTP) * _Point * 10;
        B = isPositionBuy ? initialPrice : initialPrice + buySellDiff * _Point * 10;
        C = isPositionBuy ? initialPrice - buySellDiff * _Point * 10 : initialPrice;
        D = isPositionBuy ? initialPrice - (buySellDiff + sellTP) * _Point * 10 : initialPrice - sellTP * _Point * 10;
    
        ObjectCreate(0, "A", OBJ_HLINE, 0, 0, A);
        ObjectSetInteger(0, "A", OBJPROP_COLOR, clrGreen);
        ObjectCreate(0, "B", OBJ_HLINE, 0, 0, B);
        ObjectSetInteger(0, "B", OBJPROP_COLOR, clrGreen);
        ObjectCreate(0, "C", OBJ_HLINE, 0, 0, C);
        ObjectSetInteger(0, "C", OBJPROP_COLOR, clrGreen);
        ObjectCreate(0, "D", OBJ_HLINE, 0, 0, D);
        ObjectSetInteger(0, "D", OBJPROP_COLOR, clrGreen);
    
        ENUM_ORDER_TYPE positionType = isPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
        double SL = isPositionBuy ? C : B;
        double TP = isPositionBuy ? A : D;
        CTrade trade;
        trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, SL, TP);
        
        lastPositionLotSize = initialLotSize;
        if(trade.ResultRetcode() == 10009) hedgeCycleRunning = true;
        isPositionBuy = isPositionBuy ? false : true;
       }
    //+------------------------------------------------------------------+
    

    Con esto concluimos el tema de la automatización de nuestra estrategia de cobertura clásica.


    Comprobación histórica de una estrategia de cobertura clásica

    Ahora que hemos creado un asesor para seguir automáticamente nuestra estrategia, vamos a probarlo y ver los resultados.

    Utilizaremos los siguientes parámetros de entrada para probar nuestra estrategia:

    1. initialBuyPosition: true
    2. buyTP: 15
    3. sellTP: 15
    4. buySellDiff: 15
    5. initialLotSize: 0,01
    6. lotSizeMultiplier: 2,0

    Lo probaremos en EURUSD desde el 1 de enero de 2023 hasta el 6 de diciembre de 2023 con un apalancamiento de 1:500 y un depósito de 10.000 $. Si le interesa el marco temporal, este no importa para nuestra estrategia, así que elegiremos cualquiera (no afectará en modo alguno a nuestros resultados). Veamos los resultados a continuación:


    solo con mirar el gráfico, podríamos pensar que se trata de una estrategia rentable, pero veamos otros datos y discutamos algunas cosas sobre el gráfico:

    Como podemos ver, nuestro beneficio neto ha sido de 1.470,62 USD, nuestro beneficio bruto ha sido de 13.153,68 USD y nuestra pérdida bruta ha sido de 11.683,06 USD.

    Además, vamos a analizar la reducción del balance y la equidad:

    Reducción Absoluta del Balance (Balance Drawdown Absolute) 1170,10 USD
    Balance Drawdown Maximal 1563,12 USD (15,04%)
    Reducción Relativa del Balance (Balance Drawdown Relative) 15,04% (1563,13 USD)
    Reducción Absoluta de la Equidad (Equity Drawdown Absolute) 2388,66 USD
    Reducción Máxima de la Equidad (Equity Drawdown Maximal) 2781,97 USD (26,77%)
    Reducción Relativa de la Equidad (Equity Drawdown Relative) 26,77% (2781,97 USD)

    Vamos a analizar estos términos:

    1. La reducción de balance absoluta es la diferencia entre el capital inicial, que en nuestro caso es de 10 000 USD, menos el balance mínimo, que es el punto de balance más bajo (balance mínimo).
    2. La reducción máxima de balance es la diferencia entre el punto de balance más alto (balance máximo) y el punto de balance más bajo (balance mínimo).
    3. La reducción máxima del balance es el porcentaje de reducción máxima del balance desde el punto de balance más alto (balance máximo).

    Las definiciones de equidad son simétricas:

    1. La reducción absoluta es la diferencia entre el capital inicial, que en nuestro caso es de 10.000 USD, menos el capital mínimo, que es el punto más bajo del capital (capital mínimo).
    2. La reducción máxima del balance es la diferencia entre el punto más alto del capital (capital máximo) y el punto más bajo del capital (capital mínimo).
    3. La reducción relativa del balance es el porcentaje de la reducción máxima de la equidad desde el punto más alto de la equidad (pico de la equidad).

    A continuación le mostramos las fórmulas de los seis parámetros:


    Analizando los datos anteriores, las reducciones del balance son menos preocupantes, ya que se solapan con las reducciones de equidad. En cierto sentido, podemos decir que las reducciones del balance son subconjuntos de las reducciones de la equidad. Además, la reducción de equidad es nuestro mayor problema cuando seguimos nuestra estrategia porque duplicamos el tamaño del lote en cada orden, lo que provoca un aumento exponencial del tamaño del lote. Esto puede visualizarse en el siguiente recuadro:

    Número de posiciones abiertas Tamaño de lote de la posición siguiente (aún no abierta) Margen requerido para la siguiente posición (EURUSD) 
    0 0,01 2,16 USD.
    1 0,02 4,32 USD
    2 0,04 8,64 USD
    3 0,08 17,28 USD
    4 0,16 34,56 USD
    5 0,32 69,12 USD.
    6 0,64 138,24 USD
    7 1,28 276,48 USD.
    8 2,56 552,96 USD
    9 5,12 1105,92 USD
    10 10,24 2211,84 USD
    11 20,48 4423,68 USD
    12 40,96 8847,36 USD
    13 80,92 17.694,72 USD
    14 163,84 35.389,44 USD

    En nuestro estudio, actualmente utilizamos EURUSD como par de negociación. Debemos señalar que el margen requerido para un tamaño de lote de 0,01 es de 2,16 USD, aunque esta cifra está sujeta a cambios.

    Durante la investigación, hemos observado una tendencia notable: el margen requerido para las posiciones posteriores aumenta exponencialmente. Por ejemplo, después de la duodécima orden, hemos tenido dificultades financieras. Así, el margen necesario se dispara a 17.694,44 USD, suponiendo que nuestra inversión inicial sea de 10.000 USD. Este escenario ni siquiera tiene en cuenta nuestros stop loss.

    Vamos a profundizar en este problema. Si hubiéramos incluido stop loss fijados en 15 pips por operación e incurrido en pérdidas en las 12 primeras operaciones, nuestro tamaño de lote acumulado habría sido la asombrosa cifra de 81,91 (la suma de las filas: 0,01+0,02+0,04+...+ 20,48+40,96). Esto supone una pérdida total de 12.286,5 USD; esta se calcula utilizando un valor de EURUSD de 1 USD por 10 pips para un tamaño de lote de 0,01. El cálculo es sencillo: (81,91/0,01) * 1,5 = 12.286,5 USD. La pérdida no solo supera nuestro capital inicial, sino que hace imposible mantener 12 posiciones en un ciclo con una inversión de 10.000 USD en EURUSD.

    Veamos un escenario ligeramente distinto: ¿es posible mantener 11 posiciones con nuestros 10.000 USD en EURUSD?

    Imaginemos que hemos alcanzado 10 posiciones. Esto significará que ya hemos topado con stop loss en 9 posiciones y estamos a punto de perder la décima. Si planeamos abrir la undécima posición, el tamaño total del lote para 10 posiciones será de 10,23, lo que provocará unas pérdidas de 1534,5 USD. El cálculo se realiza como antes, considerando el tipo de cambio EURUSD y el tamaño del lote. El margen necesario para la siguiente posición será de 4.423,68 USD. Sumando estas cantidades, obtendremos 5.958,18 USD, muy por debajo de nuestro umbral de 10.000 USD. Por lo tanto, resulta más que posible sobrevivir a 10 posiciones y abrir una undécima.

    No obstante, nos surge la pregunta: con los mismos 10.000 USD, ¿será posible aumentar el número total de posiciones a 12?

    Para ello, supongamos que hemos alcanzado el límite de 11 posiciones. Aquí ya hemos sufrido pérdidas en 10 posiciones y estamos a punto de perder una undécima. El tamaño total del lote para estas 11 posiciones será de 20,47, lo que supone unas pérdidas de 3070,5 USD. Si añadimos el margen necesario para la 12ª posición, que asciende a unos impresionantes 8.847,36 USD, nuestros costes totales se dispararán hasta los 11.917,86 USD, superando nuestra inversión inicial. Así pues, está claro que abrir una duodécima posición resultaría ruinoso desde el punto de vista financiero. Ya habríamos perdido 3070,5 USD, lo cual nos dejaría con 6929,5 USD.

    Podemos ver en las estadísticas de las pruebas que la estrategia se encuentra peligrosamente cerca del colapso incluso con una inversión de 10.000 USD en una divisa relativamente estable como el EURUSD. El máximo de pérdidas consecutivas es 10, lo cual indica que hemos estado a pocos pips de una desastrosa 11ª posición. Si la undécima posición también alcanza su stop loss, la estrategia colapsará, provocando pérdidas significativas.

    En nuestro informe, la reducción absoluta se observa en el nivel de 2388,66 USD. Si hubiéramos alcanzado el stop loss en la posición 11, nuestras pérdidas habrían aumentado a 3070,5 USD. En este caso, solo nos separarían de un fracaso total de la estrategia 681,84 USD (3070,5-2388,66).

    Sin embargo, hay un factor crucial que hasta ahora hemos pasado por alto: el spread. Esta variable puede tener un impacto significativo en los beneficios, como demuestran los dos estudios de casos de nuestro informe que vemos en la imagen siguiente.

    Observe las zonas marcadas en rojo. En estos casos, a pesar de ganar la transacción (ganar corresponde a obtener el mayor lote en la última transacción), no conseguimos ningún beneficio. Esta anomalía se explica por el spread. Su naturaleza volátil complica aún más nuestra estrategia, que requerirá un análisis más profundo en la siguiente parte de esta serie.

    También deberemos tener en cuenta las limitaciones de la estrategia de cobertura clásica. Una desventaja importante es la gran capacidad de retención necesaria para mantener un gran número de órdenes si no se alcanza antes el nivel de take profit. Esta estrategia solo puede ofrecer beneficios si el multiplicador del tamaño del lote es 2 o más (en el caso de buyTP = SellTP = buySellDiff, sin contar el spread). Si es inferior a 2, corremos el riesgo de entrar en negativo cuando aumente el número de órdenes. En la próxima parte de nuestra serie, analizaremos esta dinámica y cómo optimizar una estrategia de cobertura clásica para obtener el máximo beneficio.


    Conclusión

    En la primera parte de nuestra serie, hemos analizado una estrategia bastante compleja, y también la hemos automatizado usando un asesor experto MQL5. La estrategia es potencialmente rentable, aunque la colocación de posiciones con lotes más grandes requiere una gran capacidad para mantener la posición, lo cual no siempre resulta factible para el inversor, y también es muy arriesgada, ya que existe la posibilidad de una gran reducción. Para superar estas limitaciones, debemos optimizar la estrategia, cosa que haremos en la siguiente parte de la serie.

    Hasta ahora hemos utilizado valores fijos aleatorios de lotSizeMultiplier, initialLotSize, buySellDiff, sellTP y buyTP, pero podemos optimizar esta estrategia y encontrar los valores óptimos de estos parámetros de entrada que nos darán la mayor rentabilidad posible. También averiguaremos si inicialmente es rentable empezar comprando o vendiendo, lo cual también puede depender de las distintas divisas y condiciones del mercado. En la próxima parte de la serie hablaremos de muchas más cosas, así que permanezca atento a las novedades. 

    ¡Gracias por su tiempo! Espero que la información le haya sido útil. Si tiene alguna idea o sugerencia que le gustaría ver en el próximo artículo, envíeme un correo electrónico.

    ¡Diviértase programando y comerciando!


    Traducción del inglés realizada por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/en/articles/13845

    Archivos adjuntos |
    Algoritmos de optimización de la población: Algoritmo de recocido isotrópico simulado (Simulated Isotropic Annealing, SIA). Parte II Algoritmos de optimización de la población: Algoritmo de recocido isotrópico simulado (Simulated Isotropic Annealing, SIA). Parte II
    En la primera parte del artículo, hablamos del conocido y popular algoritmo del recocido simulado, analizamos sus ventajas y describimos detalladamente sus desventajas. La segunda parte del artículo se dedicará a la transformación cardinal del algoritmo y su renacimiento en un nuevo algoritmo de optimización, el "recocido isotrópico simulado, SIA".
    Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios
    Conozca los paradigmas de programación y su aplicación en el código MQL5. En este artículo, analizaremos las características de la programación procedimental y ofreceremos ejemplos prácticos. Asimismo, aprenderemos a desarrollar un asesor basado en la acción del precio (Action Price) utilizando el indicador EMA y datos de velas. Además, el artículo introduce el paradigma de la programación funcional.
    Aprendizaje automático y Data Science (Parte 16): Una nueva mirada a los árboles de decisión Aprendizaje automático y Data Science (Parte 16): Una nueva mirada a los árboles de decisión
    En la última parte de nuestra serie sobre aprendizaje automático y trabajo con big data, vamos a volver a los árboles de decisión. Este artículo va dirigido a los tráders que desean comprender el papel de los árboles de decisión en el análisis de las tendencias del mercado. Asimismo, contiene toda la información básica sobre la estructura, la finalidad y el uso de estos árboles. Hoy analizaremos las raíces y ramas de los árboles algorítmicos y veremos cuál es su potencial en relación con las decisiones comerciales. También echaremos juntos un nuevo vistazo a los árboles de decisión y veremos cómo pueden ayudarnos a superar los retos de los mercados financieros.
    Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 5): Bandas de Bollinger en el Canal de Keltner - Señales de Indicador Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 5): Bandas de Bollinger en el Canal de Keltner - Señales de Indicador
    En este artículo, entenderemos por asesor multidivisa un asesor o robot comercial que puede comerciar (abrir/cerrar órdenes, gestionar órdenes, por ejemplo, trailing-stop y trailing-profit, etc.) con más de un par de símbolos de un gráfico. En este artículo, usaremos las señales de dos indicadores, las Bandas de Bollinger® y el Canal de Keltner.