English Русский Español Deutsch 日本語
preview
EA de grid-hedge modificado em MQL5 (Parte I): Criando um EA de hedge simples

EA de grid-hedge modificado em MQL5 (Parte I): Criando um EA de hedge simples

MetaTrader 5Sistemas de negociação | 22 maio 2024, 11:12
189 0
Kailash Bai Mina
Kailash Bai Mina

Introdução

Você está procurando mergulhar no mundo do trading com Expert Advisors (EAs) mas continua esbarrando na frase: "Não usa Hedge/Grid/Martingale"? Você pode se perguntar por que tanto alvoroço em torno dessas estratégias. Por que as pessoas continuam dizendo que são arriscadas e qual é a verdadeira questão por trás dessas afirmações? Talvez você esteja até pensando: "Ei, podemos ajustar essas estratégias para torná-las mais seguras?" E, por que os traders se preocupam com essas estratégias em primeiro lugar? Quais são os prós e contras delas? Se esses pensamentos já passaram pela sua cabeça, você está no lugar certo.

Começaremos criando um Expert Advisor de hedge simples. Pense nisso como o primeiro passo em direção ao nosso projeto maior, um Expert Advisor de Grid-Hedge. Será uma mistura interessante das estratégias clássicas de grade e hedge. Ao final deste artigo, você saberá como montar uma estratégia básica de hedge e entenderá se essa estratégia é tão lucrativa quanto dizem.

Mas não vamos parar por aí. Ao longo desta série, exploraremos essas estratégias a fundo. Nosso objetivo? Ver se podemos pegar essas estratégias tradicionais, dar uma nova cara e usá-las para obter lucros sólidos com trading automatizado.

Aqui está uma visão geral rápida do que abordaremos neste artigo:

  1. Discutindo a estratégia de hedge clássica
  2. Automação da estratégia de hedge clássica
  3. Backtesting da nossa estratégia de hedge clássica
  4. Conclusão


Discutindo a estratégia de hedge clássica

Primeiro e antes de mais nada, devemos discutir a estratégia antes de prosseguir.

Primeiro, abrimos uma posição de compra, digamos, no nível de preço 1000 e colocamos o stop loss em 950 e o take profit em 1050. Ou seja, se atingirmos o stop loss, perderemos US $50, e se atingirmos o take profit, ganharemos US $50. Agora, se atingirmos o take profit, a estratégia termina aqui e vamos para casa com lucro. Mas, caso atinja o stop loss, perderemos US $50. O que faremos então é abrir uma posição de venda imediatamente em 950 e definir o take profit em 900 e o stop loss em 1000. Se essa nova posição de venda atingir o take profit, ganharemos US $50, mas a questão é que já perdemos US $50, então nosso ganho líquido é US $0. Se atingir o stop loss no nível de preço 1000, perderemos mais US $50, totalizando uma perda de US $100, mas agora, nesse ponto, abriremos novamente uma posição de compra com o mesmo take profit e stop loss da posição de compra anterior. Se esta nova posição de compra atingir o TP, obteremos US $50 e nosso ganho líquido será -$50-$50 +$50 = -$50, ou seja, uma perda de US $50. E se atingir o stop loss, nosso total será -$50-$50-$50 = -$150, ou seja, uma perda total de US $150.

Para simplificar, ignoramos os spreads e as comissões por enquanto.

Agora você pode estar pensando: "O que está acontecendo aqui e como vamos obter 100% assim?" Mas você está deixando de considerar uma coisa importante: o tamanho do lote. E se aumentarmos o tamanho do lote das posições consecutivas? Então, vamos revisar nossa estratégia.

Abrimos uma posição de compra de 0,01 lote (mínimo possível) a 1000:

  • se atingirmos o take profit (1050), vamos para casa com um lucro de US $50 e a estratégia termina aqui.
  • se atingirmos o stop loss (950), vamos para casa com uma perda de US $50 e a estratégia termina aqui.

De acordo com nossa estratégia, abriremos imediatamente uma posição de venda de 0,02 lote (dobrado) a 950:

  • Se atingirmos o take profit (900), ganharemos um lucro líquido total de -$50 +$100 = US $50 e a estratégia termina aqui.
  • Se atingirmos o stop loss (1000), perderemos US $50 +$100 = US $150 no total.

De acordo com nossa estratégia, abriremos imediatamente uma posição de compra de 0,04 lote (novamente dobrado) a 1000:

  • Se atingirmos o take profit (1050), ganharemos um lucro líquido total de -$50-$100 +$200 = US $50 e a estratégia termina aqui.
  • Se atingirmos o stop loss (950), perderemos $50 +$100 +$150 = US $350 no total.

De acordo com nossa estratégia, abriremos imediatamente uma posição de venda de 0,08 lote (novamente dobrado) a 950:

  • Se atingirmos o take profit (900), ganharemos um lucro líquido total de -$50-$100-$150 +$400 = US $50 e a estratégia termina aqui.
  • Se atingirmos o stop loss (1000), perderemos $50 +$100 +$150 +$200 = US $500 no total.

... 

Como você já deve ter notado, em qualquer caso, obtemos um lucro de US $50 quando a estratégia termina. Caso contrário, a estratégia continua. Esta estratégia continuará até atingirmos o take profit em 900 ou 1050; o preço eventualmente atingirá um desses dois pontos e obteremos um lucro garantido de US $50.

No caso acima, abrimos uma posição de compra primeiro, mas não é obrigatório começar com uma posição de compra. Alternativamente, podemos iniciar a estratégia com uma posição de venda de 0,01 lote (no nosso caso).

Na verdade, essa alternativa de começar com uma posição de venda é muito importante, pois modificaremos a estratégia mais tarde para obter o máximo de flexibilidade possível. Por exemplo, podemos precisar definir um ponto de entrada (compra inicial no nosso caso) para o ciclo acima, mas restringir esse ponto de entrada apenas à posição de compra será problemático, pois podemos definir o ponto de entrada de forma que seja benéfico abrir uma posição de venda inicialmente.

A estratégia começando com uma posição de venda será exatamente simétrica ao caso acima de começar com uma posição de compra. Para explicar isso de forma mais clara, nossa estratégia ficará algo assim:

  • se atingirmos o take profit (900), vamos para casa com um lucro de US $50 e a estratégia termina aqui.
  • se atingirmos o stop loss (1000), perderemos US $50.

De acordo com nossa estratégia, abriremos imediatamente uma posição de compra de 0,02 lote (dobrado) a 1000:

  • Se atingirmos o take profit (1050), ganharemos um lucro líquido total de -$50 +$100 = US $50 e a estratégia termina aqui.
  • se atingirmos o stop loss (950), perderemos US $50 +$100 = US $150 no total.

De acordo com nossa estratégia, abriremos imediatamente uma posição de venda de 0,04 lote (novamente dobrado) a 950:

  • se atingirmos o take profit (900), ganharemos um lucro líquido total de -$50-$100 +$200 = US $50 e a estratégia termina aqui.
  • se atingirmos o stop loss (1000), perderemos $50 +$100 +$150 = US $350 no total.

... e assim por diante.

Novamente, como podemos ver, essa estratégia termina apenas quando atingimos os níveis de preço 900 ou 1050, levando a um lucro de US $50 com certeza. Se não atingirmos esses níveis de preço, a estratégia continua até eventualmente chegarmos a eles.

Nota: Não é necessário aumentar o tamanho do lote em um fator de 2. Podemos aumentá-lo com qualquer multiplicador, embora qualquer multiplicador menor que 2 não garanta lucro em nenhum dos casos acima. Escolhemos 2 por simplicidade e poderíamos alterar esse valor ao otimizar ainda mais a estratégia.

Então, isso conclui nossa discussão sobre a estratégia de hedge clássica.


Automação da estratégia de hedge clássica

Primeiro, precisamos discutir o plano de como procederemos com a criação de um Expert Advisor. Na verdade, existem muitas abordagens para fazer isso. As duas abordagens principais são as seguintes:

  • Abordagem nº1: Definir quatro níveis (variáveis) conforme indicado pela estratégia e colocar posições sempre que essas linhas forem cruzadas pelo preço novamente, conforme indicado por nossa estratégia.
  • Abordagem nº2: Usar ordens pendentes e detectar quando essa ordem pendente é executada, colocando outras ordens pendentes quando isso acontecer.

    Ambas as abordagens são quase equivalentes, e é discutível qual delas é ligeiramente melhor, mas discutirei apenas a Abordagem nº1, pois é mais fácil de codificar e entender.


    Automação da estratégia de hedge clássica

    Primeiro, declararemos algumas variáveis no espaço 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 é uma variável booleana que decidirá qual tipo de posição será colocada em seguida, ou seja, posição de compra ou posição de venda. Se for verdadeira, o próximo tipo de posição será Compra e, caso contrário, Venda.
    2. buyTP é a distância entre A e B, que é o take profit das posições de compra (em pips), onde A e B serão definidos posteriormente.
    3. sellTP é a distância entre C e D, que é o take profit das posições de venda (em pips), onde C e D serão definidos posteriormente.
    4. buySellDiff é a distância entre B e C, que é o nível de preço de compra e o nível de preço de venda (em pips).
    5. intialLotSize é o tamanho do lote da primeira posição.
    6. lotSizeMultiplier é o multiplicador do tamanho do lote para a posição consecutiva.
    A, B, C e D são basicamente níveis de preço em ordem decrescente de cima para baixo.

    Nota: As variáveis serão usadas posteriormente para otimizar a estratégia.

    Por exemplo, definimos buyTP, SellTP e buySellDiff iguais a 15 pips, mas vamos alterá-los mais tarde e ver quais valores nos darão o lucro e o drawdown ideais.

    Essas são as variáveis de entrada que serão usadas para otimização posteriormente.

    Agora, criaremos mais algumas variáveis no espaço global:

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

    1. Primeiro, definimos 4 níveis chamados A, B, C, D como variáveis do tipo double:
      • A: Representa o nível de preço do take profit para todas as posições de compra.
      • B: Representa o nível de preço de abertura para todas as posições de compra e o stop loss de todas as posições de venda.
      • C: Representa o nível de preço de abertura para todas as posições de venda e o stop loss de todas as posições de compra.
      • D: Representa o nível de preço do take profit para todas as posições de venda.
    2. isPositionBuy: Esta é uma variável booleana que pode assumir 2 valores, verdadeiro e falso, onde verdadeiro representa que a posição inicial é de compra e falso representa que a posição inicial é de venda.
    3. hedgeCycleRunning: Esta é uma variável booleana que também pode assumir 2 valores, verdadeiro e falso, onde verdadeiro representa que um ciclo de hedge está em andamento, ou seja, a ordem inicial foi aberta, mas os níveis de preço A ou D que definimos acima ainda não foram atingidos, enquanto falso representa que o nível de preço atingiu A ou D e um novo ciclo será iniciado, como veremos mais tarde. Além disso, esta variável será falsa por padrão.
    4. lastPositionLotSize: Como o nome sugere, esta variável do tipo double sempre contém o tamanho do lote da última ordem aberta e, caso o ciclo não tenha sido iniciado, assumirá o valor igual à variável de entrada initialLotSize, que definiremos mais tarde.

    Agora, criaremos a função abaixo: 
    //+------------------------------------------------------------------+
    //| 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;
       }
    //+------------------------------------------------------------------+

    O tipo de função é void, ou seja, não precisamos que ela retorne nada. A função funciona da seguinte forma:

    Primeiro, definimos a variável isPositionBuy (bool) igual à variável de entrada initialPositionBuy, que nos dirá qual tipo de posição colocar no início de cada ciclo. Você pode estar se perguntando por que precisamos de duas variáveis se ambas são iguais, mas observe que alteraremos isPositionBuy alternadamente (última linha do bloco de código acima). No entanto, initialPositionBuy é sempre fixa e não a alteramos.

    Em seguida, definimos uma nova variável (do tipo double) chamada initialPrice, que definimos igual ao Ask ou Bid usando um operador ternário. Se isPositionBuy for verdadeiro, então initialPrice será igual ao preço Ask naquele momento e, caso contrário, ao preço Bid.

    Depois, definimos as variáveis (do tipo double) que discutimos brevemente anteriormente, ou seja, as variáveis A, B, C, D, usando o operador ternário como segue:

    1. Se isPositionBuy for verdadeiro:
      • A é igual à soma de initialPrice e buyTP (variável de entrada), onde buyTP é multiplicado pelo fator de (_Point*10), onde _Point é na verdade a função predefinida "Point()".
      • B é igual a initialPrice.
      • C é igual a initialPrice menos buySellDiff (variável de entrada), onde buySellDiff é multiplicado pelo fator de (_Point*10).
      • D é igual a initialPrice menos a soma de buySellDiff e sellTP, onde sellTP é multiplicado pelo fator de (_Point*10).

    2. Se isPositionBuy for falso:
      • A é igual à soma de initialPrice e (buySellDiff + buyTP), onde este resultado é multiplicado pelo fator de (_Point*10).
      • B é igual à soma de initialPrice e buySellDiff, onde buySellDiff é multiplicado pelo fator de (_Point*10).
      • C é igual a initialPrice.
      • D é igual a initialPrice menos sellTP, onde sellTP é multiplicado pelo fator de (_Point*10).

    Agora, para visualização, desenhamos algumas linhas no gráfico representando os níveis de preço A, B, C, D usando ObjectCreate e configuramos sua propriedade de cor para clrGreen usando ObjectSetInteger (você pode usar qualquer outra cor também).

    Agora precisamos abrir a ordem inicial, que pode ser de compra ou venda, dependendo da variável isPositionBuy. Para fazer isso, definimos três variáveis: positionType, SL, TP.

    1. positionType: O tipo dessa variável é ENUM_ORDER _TYPE, que é um tipo de variável personalizado predefinido que pode assumir valores inteiros de 0 a 8 de acordo com a seguinte tabela:

      Valores inteiros 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 você pode ver, 0 representa ORDER_TYPE_BUY e 1 representa ORDER_TYPE_SELL, só precisamos desses dois. Usaremos identificadores em vez de valores inteiros, pois são difíceis de lembrar.

    2. SL: se isPositionBuy isPositionBuy for verdadeiro, então SL é igual ao nível de preço C, caso contrário, é igual a B.

    3. TP: se isPositionBuy isPositionBuy for verdadeiro, então TP é igual ao nível de preço A, caso contrário, é igual a D.

    Usando essas 3 variáveis, precisamos colocar uma posição da seguinte maneira:

    Primeiro, importamos a biblioteca de trade padrão usando #include:

    #include <Trade/Trade.mqh>
    

    Agora, logo antes de abrir a posição, criamos uma instância da classe CTrade usando:

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

    E usando essa instância, colocamos uma posição com a função PositionOpen naquela instância com os seguintes parâmetros:

    1. _Symbol fornece o símbolo atual ao qual o Expert Advisor está anexado.
    2. positionType é a variável ENUM_ORDER_TYPE que definimos anteriormente.
    3. O tamanho inicial do lote é a variável de entrada.
    4. initialPrice é o preço de abertura da ordem, que é Ask (para posições de compra) ou Bid (para posições de venda).
    5. Finalmente, fornecemos os níveis de preços de SL e TP.

    Com isso, uma posição de Compra ou Venda será colocada. Agora, após a posição ser colocada, armazenamos seu Tamanho de Lote na variável definida no espaço global chamada lastPositionLotSize para que possamos usar este lote e o multiplicador da entrada para calcular o tamanho do lote de posições futuras.

    Com isso, restam 2 coisas para fazer:

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

    Aqui, definimos o valor de hedgeCycleRunning como verdadeiro apenas quando uma posição é colocada com sucesso. Isso é determinado pela função ResultRetcode() na instância CTrade chamada trade, que retorna "10009" indicando uma colocação bem-sucedida (você pode ver todos esses códigos de retorno aqui). A razão para usar hedgeCycleRunning será explicada pelo código adicional.

    Uma última coisa é que estamos usando o operador ternário para alternar o valor de isPositionBuy. Se era falso, se tornará verdadeiro e vice-versa. Fazemos isso porque nossa estratégia afirma que, uma vez que uma posição inicial é aberta, uma venda será colocada após uma compra e uma compra será colocada após uma venda, significando que alterna.

    Isso conclui nossa discussão sobre nossa função fundamentalmente importante StartHedgeCycle(), pois usaremos essa função repetidamente.

    Agora, vamos prosseguir com nossa última parte do 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;
           }
       }
    //+------------------------------------------------------------------+

    As duas primeiras linhas são autoexplicativas, elas apenas definem _Ask e _Bid (variáveis do tipo double) que armazenam Ask e Bid naquele momento.

    Em seguida, usamos uma declaração if para iniciar o ciclo de hedge usando a função StartHedgeCycle() se a variável hedgeCycleRunning for falsa. Já sabemos o que a função StartHedgeCycle() faz, mas, para resumir, ela faz o seguinte:

    1. Definir os níveis de preços A, B, C e D.
    2. Desenhar linhas horizontais verdes nos níveis de preços A, B, C e D para visualização.
    3. Abrir uma posição.
    4. Armazenar o Tamanho de Lote dessa posição na variável lastPositionLotSize definida no espaço global para que possa ser usada em qualquer lugar.
    5. Definir hedgeCycleRunning como verdadeiro, pois estava falso antes, e é exatamente por isso que executamos a função StartHedgeCycle().
    6. Finalmente, alternar a variável isPositionBuy de true para false e de false para true, conforme nossa estratégia.

    Executamos StartHedgeCycle() apenas uma vez porque o executamos se hedgeCycleRunning estava falso, e no final da função, mudamos para verdadeiro. Portanto, a menos que definamos hedgeCycleRunning como falso novamente, StartHedgeCycle() não será executado novamente.

    Vamos pular as duas próximas declarações if por enquanto e voltaremos a elas depois. Vamos ver a última declaração if:

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

    Isso garante que o ciclo seja reiniciado. Como discutimos anteriormente, se definirmos hedgeCycleRunning como verdadeiro, o ciclo reiniciará e tudo o que discutimos anteriormente acontecerá novamente. Além disso, eu garanti que quando o ciclo reiniciar, todas as posições do ciclo anterior serão fechadas com lucro ou prejuízo (seja uma posição de Compra ou Venda).

    Portanto, lidamos com o início do ciclo, o fim do ciclo e o reinício, mas ainda falta a parte principal, que é lidar com a abertura da ordem quando o preço atinge o nível B de baixo para cima ou o nível C de cima para baixo. O tipo de posição também deve ser alternado, onde Compra só abre no nível B e Venda só abre no nível C.

    Pulamos o código que lida com isso, então vamos voltar a ele.

    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;
       }

     Então, essas duas declarações if lidam com as aberturas de ordens entre o ciclo (entre a posição inicial ser colocada e o ciclo ainda não ter terminado).

    1. Primeira declaração IF: Esta lida com a abertura de uma ordem de venda. Se a variável Bid (que contém o preço Bid naquele momento) estiver abaixo ou igual ao nível C e isPositionBuy for falso, então definimos uma variável double chamada newPositionLotSize. Isso é igual ao lastPositionLotSize multiplicado pelo lotSizeMultiplier, e então o valor double é normalizado até 2 casas decimais usando uma função predefinida chamada NormalizeDouble.

      Em seguida, colocamos uma posição de venda usando a função predefinida PositionOpen() da instância CTrade chamada trade, fornecendo o newPositionLotSize como parâmetro. Finalmente, definimos lastPositionLotSize para esse novo tamanho de lote (sem normalização) para que possamos multiplicá-lo em posições futuras e, por fim, alternamos isPositionBuy de true para false ou de false para true. 

    2. Segunda declaração IF: Esta lida com a abertura de uma ordem de compra. Se o preço Ask (variável que contém o preço Ask naquele momento) for igual ou superior ao nível B e isPositionBuy for verdadeiro, então definimos uma variável double chamada newPositionLotSize. Definimos newPositionLotSize igual ao lastPositionLotSize multiplicado por lotSizeMultiplier e normalizamos o valor double para duas casas decimais usando a função predefinida NormalizeDouble, como antes.

      Em seguida, colocamos uma posição de compra usando a função predefinida PositionOpen() da instância CTrade chamada trade, fornecendo o newPositionLotSize como parâmetro. Finalmente, definimos lastPositionLotSize para esse novo tamanho de lote (sem normalização) para que possamos multiplicá-lo em posições futuras. Por último, alternamos isPositionBuy de true para false ou de false para true.

    Há 2 pontos muito importantes a serem observados aqui:

    • Na primeira Declaração IF, usamos o preço "Bid" e declaramos abrir uma posição quando "Bid" fica abaixo ou igual a "C" e "isBuyPosition" é falso. Por que usamos "Bid" aqui?

      Suponha que usamos o preço Ask, então há a possibilidade de que a posição de compra anterior seja fechada, mas a nova posição de venda não seja aberta. Isso ocorre porque sabemos que a compra abre em Ask e fecha em Bid, deixando a possibilidade de que a compra seja fechada quando Bid cruzar ou igualar a linha do nível de preço C de cima para baixo. Isso fechará a ordem de compra pelo stop loss que definimos anteriormente ao abrir a posição, mas a venda ainda não foi aberta. Então, se o preço, que é ask e bid, subir, nossa estratégia não seria seguida. Essa é a razão pela qual usamos Bid em vez de Ask.

      Simetricamente, na segunda Declaração IF, usamos o preço Ask e declaramos que abriríamos uma posição quando Ask ultrapassar ou igualar B e isBuyPosition for verdadeiro. Por que usamos Ask aqui?

      Suponhamos que usemos o preço de Bid, então há uma possibilidade de que a posição de venda anterior seja fechada, mas a nova posição de compra não seja aberta. Sabemos que a venda abre no Bid e fecha no Ask, o que deixa a possibilidade de que a venda seja fechada quando o Ask cruzar ou igualar à linha de nível de preço B de baixo para cima, fechando assim a ordem de venda pelo stop loss que definimos anteriormente ao abrir a posição. No entanto, a compra ainda não foi aberta. Então, se o preço de Ask e Bid ambos caírem, nossa estratégia não será seguida. Por isso usamos Ask em vez de Bid.

      Então, o ponto principal é que, se uma posição de Compra/Venda for fechada, uma posição consecutiva de Venda/Compra deve ser aberta imediatamente para que a estratégia seja seguida corretamente.

    • Em ambas as instruções IF, mencionamos que ao definir o valor de lastPositionLotSize, o igualamos a (lastPositionLotSize * lotSizeMultiplier) em vez de newPositionLotSize, que é igual ao valor normalizado de (lastPositionLotSize * lotSizeMultiplier) até duas casas decimais usando a função NormalizeDouble() predefinida.
      NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2)
      Bem, por que fizemos isso? Na verdade, se igualarmos ao valor normalizado, nossa estratégia será seguida corretamente porque, por exemplo, suponhamos que definimos o tamanho inicial do lote como 0.01 e o multiplicador como 1.5, então o primeiro tamanho do lote será, obviamente, 0.01 e o próximo será 0.01*1.5 = 0.015. Agora, claro, não podemos abrir um tamanho de lote de 0.015 que não é permitido pela corretora, pois o multiplicador deve ser múltiplo inteiro de 0.01, o que 0.015 não é, e é exatamente por isso que normalizamos o tamanho do lote até 2 casas decimais. Assim, 0,01 será aberto. Agora temos duas opções para definir o valor de lastPositionLotSize: ou 0.01 (0.010) ou 0.015. Suponhamos que escolhemos 0.01 (0.010), então da próxima vez que colocarmos uma posição, usaremos 0.01*1.5 = 0.015. Após a normalização, torna-se 0.01 e isso continua indefinidamente. Então, usamos o multiplicador 1.5 e começamos com o tamanho de lote de 0.01, mas o tamanho do lote nunca aumentou e ficamos presos em um loop, todas as posições sendo colocadas com um tamanho de lote de 0.01. Isso implica que não devemos igualar lastPositionLotSize a 0.01 (0.010), então, em vez disso, escolhemos a outra opção, 0.015, ou seja, escolhemos o valor antes da normalização.

      É exatamente por isso que definimos lastPositionLotSize igual a (lastPositionLotSize * lotSizeMultiplier) em vez de NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2).

    Finalmente, nosso código completo fica assim:

    #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;
       }
    //+------------------------------------------------------------------+
    

    Isso conclui nossa discussão sobre como automatizar nossa estratégia de hedge clássico.


    Backtesting da nossa estratégia de hedge clássica

    Agora que criamos o Expert Advisor para seguir nossa estratégia automaticamente, é lógico testá-lo e ver os resultados.

    Vou usar os seguintes parâmetros de entrada para testar nossa estratégia:

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

    Vou testá-la no EURUSD de 1 de janeiro de 2023 a 6 de dezembro de 2023 com uma alavancagem de 1:500 e um depósito de US $10.000 e, se você estiver se perguntando sobre o timeframe, ele é irrelevante para nossa estratégia, então escolherei qualquer um (não afetará nossos resultados). Vamos ver os resultados abaixo:


    Só de olhar o gráfico, você pode pensar que esta é uma estratégia lucrativa, mas vamos olhar outros dados e discutir alguns pontos no gráfico:

    Como você pode ver, tivemos um lucro líquido de US $1470.62, onde o lucro bruto foi US $13,153.68 e a perda bruta foi US $11,683.06.

    Além disso, vamos dar uma olhada no rebaixamento do saldo e do patrimônio:

    Rebaixamento absoluto do saldo (Balance Drawdown Absolute) $1170.10
    Rebaixamento máximo do saldo (Balance Drawdown Maximal) $1563.12 (15.04%)
    Rebaixamento relativo do saldo (Balance Drawdown Relative) 15.04% ($1563.13)
    Rebaixamento absoluto do patrimônio (Equity Drawdown Absolute) $2388.66
    Rebaixamento máximo do patrimônio (Equity Drawdown Maximal) $2781.97 (26.77%)
    Rebaixamento relativo do patrimônio (Equity Drawdown Relative) 26.77% ($2781.97)

    Vamos entender esses termos:

    1. Rebaixamento absoluto do saldo: Esta é a diferença entre o capital inicial, que é US $10.000 no nosso caso, menos o saldo mínimo, que é o ponto mais baixo do saldo (saldo de fundo).
    2. Rebaixamento máximo do saldo: Esta é a diferença entre o ponto mais alto do saldo (saldo de pico) menos o ponto mais baixo do saldo (saldo de fundo).
    3. Rebaixamento relativo do saldo: Esta é a porcentagem do rebaixamento máximo do saldo em relação ao ponto mais alto do saldo (saldo de pico).

    As definições de patrimônio são simétricas:

    1. Rebaixamento absoluto do patrimônio: Esta é a diferença entre o capital inicial, que é US $10.000 no nosso caso, menos o patrimônio mínimo, que é o ponto mais baixo do patrimônio (patrimônio de fundo).
    2. Rebaixamento máximo do patrimônio: Esta é a diferença entre o ponto mais alto do patrimônio (patrimônio de pico) menos o ponto mais baixo do patrimônio (patrimônio de fundo).
    3. Rebaixamento relativo do patrimônio: Esta é a porcentagem do rebaixamento máximo do patrimônio em relação ao ponto mais alto do patrimônio (patrimônio de pico).

    As fórmulas para todos os seis parâmetros são apresentadas abaixo:


    Analisando os dados acima, os rebaixamentos do saldo seriam a menor das nossas preocupações, pois os rebaixamentos do patrimônio cobrem isso. Em certo sentido, podemos dizer que os rebaixamentos do saldo são subconjuntos dos rebaixamentos do patrimônio. Além disso, os rebaixamentos do patrimônio são nosso maior problema ao seguir nossa estratégia, porque estamos dobrando o tamanho do lote em cada ordem, o que leva a um crescimento exponencial do tamanho do lote, conforme pode ser visualizado na tabela abaixo:

    Número de posições abertas Tamanho do lote da próxima posição (ainda não aberta) Margem necessária para a próxima posição (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

    Em nossa exploração, estamos atualmente utilizando EURUSD como nosso par de negociação. É importante notar que a margem necessária para um tamanho de lote de 0,01 é de US $2,16, embora este valor esteja sujeito a mudanças.

    À medida que nos aprofundamos, observamos uma tendência notável: a margem necessária para posições subsequentes aumenta exponencialmente. Por exemplo, após a 12ª ordem, enfrentamos um gargalo financeiro. A margem necessária sobe para US $17.694,72, um valor muito além do nosso alcance, considerando que nosso investimento inicial foi de US $10.000. Este cenário nem sequer leva em conta nossos stop-losses.

    Vamos detalhar ainda mais. Se incluirmos stop-losses, definidos em 15 pips por negociação, e tivermos perdido as primeiras 12 negociações, nosso tamanho cumulativo do lote seria um impressionante 81,91 (a soma da série: 0,01+0,02+0,04+...+20,48+40,96). Isso se traduz em uma perda total de US $12.286,5, calculada usando o valor EURUSD de US $1 por 10 pips para um tamanho de lote de 0,01. É um cálculo simples: (81,91/0,01) * 1,5 = US $12.286,5. A perda não só excede nosso capital inicial, mas também torna impossível sustentar 12 posições em um ciclo com um investimento de US $10.000 em EURUSD.

    Vamos considerar um cenário ligeiramente diferente: podemos gerenciar sustentar 11 posições totais com nossos US $10.000 em EURUSD?

    Imagine que chegamos a 10 posições totais. Isso implica que já enfrentamos stop losses em 9 posições e estamos prestes a perder a 10ª. Se planejamos abrir uma 11ª posição, o tamanho total do lote para as 10 posições seria 10,23, resultando em uma perda de US $1.534,5. Isso é calculado da mesma maneira que antes, considerando a taxa EURUSD e o tamanho do lote. A margem necessária para a próxima posição seria US $4.423,68. Somando esses valores, obtemos US $5.958,18, o que está confortavelmente abaixo do nosso limite de US $10.000. Portanto, é viável sobreviver a 10 posições totais e abrir uma 11ª.

    No entanto, surge a pergunta: é possível estender para 12 posições totais com os mesmos US $10.000?

    Para isso, vamos supor que chegamos ao limite de 11 posições. Aqui, já sofremos perdas em 10 posições e estamos à beira de perder a 11ª. O tamanho total do lote para essas 11 posições é 20,47, resultando em uma perda de US $3.070,5. Adicionando a margem necessária para a 12ª posição, que é um pesado US $8.847,36, nosso gasto total dispara para US $11.917,86, excedendo nosso investimento inicial. Portanto, fica claro que abrir uma 12ª posição com 11 já em jogo é financeiramente inviável. Teríamos já perdido US $3.070,5, o que nos deixaria com apenas US $6.929,5.

    Nas estatísticas de backtest, observamos que a estratégia está perigosamente próxima do colapso, mesmo com um investimento de US $10.000 em uma moeda relativamente estável como o EURUSD. As perdas consecutivas máximas são 10, indicando que estávamos a poucos pips da desastrosa 11ª posição. Caso a 11ª posição também atingisse seu stop loss, a estratégia desmoronaria, levando a perdas significativas.

    Em nosso relatório, o rebaixamento absoluto está marcado em US $2.388,66. Se tivéssemos alcançado o stop loss da 11ª posição, nossas perdas teriam escalado para US $3.070,5. Isso nos colocaria US $681,84 ($3.070,5-$2.388,66) abaixo de um fracasso total da estratégia.

    No entanto, há um fator crítico que negligenciamos até agora: o spread. Essa variável pode impactar significativamente os lucros, conforme evidenciado por duas instâncias específicas em nosso relatório, destacadas na imagem abaixo.

    Observe os círculos vermelhos na imagem. Nesses casos, apesar de ganhar a negociação (onde ganhar equivale a garantir o maior lote na última negociação), não conseguimos obter lucro algum. Essa anomalia é atribuída ao spread. Sua natureza variável complica ainda mais nossa estratégia, exigindo uma análise mais aprofundada na próxima parte desta série.

    Também devemos considerar as limitações da estratégia de hedge clássica. Uma desvantagem significativa é a capacidade substancial de manutenção necessária para sustentar várias ordens se o nível de take profit não for atingido cedo. Essa estratégia só pode garantir lucros se o multiplicador do tamanho do lote for 2 ou maior (no caso de buyTP = sellTP = buySellDiff, ignorando o spread). Se for menor que 2, há um risco de incorrer em lucros negativos à medida que o número de ordens aumenta. Exploraremos essas dinâmicas e como otimizar a estratégia de hedge clássica para obter retornos máximos na próxima parte da nossa série.


    Conclusão

    Então, discutimos uma estratégia bastante complexa nesta primeira parte da nossa série e também a automatizamos usando um Expert Advisor em MQL5. Pode ser uma estratégia lucrativa, embora seja necessário ter uma alta capacidade de manutenção para posicionar ordens com tamanhos de lote maiores, o que nem sempre é viável para um investidor e também é muito arriscado, pois pode ocorrer um grande rebaixamento. Para superar essas limitações, devemos otimizar a estratégia, o que será feito na próxima parte desta série.

    Até agora, usamos valores fixos arbitrários de lotSizeMultiplier, initialLotSize, buySellDiff, sellTP, buyTP, mas podemos otimizar essa estratégia e encontrar os valores ideais desses parâmetros de entrada que nos proporcionem o máximo retorno possível. Também descobriremos se é benéfico começar inicialmente com uma posição de compra ou venda, o que também pode depender de diferentes moedas e condições de mercado. Portanto, cobriremos muitas coisas úteis na próxima parte desta série, então fique ligado. 

    Obrigado por dedicar seu tempo para ler meus artigos. Espero que os ache informativos e úteis em seus empreendimentos. Se tiver alguma ideia ou sugestão sobre o que gostaria de ver no meu próximo artigo, não hesite em compartilhar.

    Feliz codificação! Feliz trading!


    Traduzido do Inglês pela MetaQuotes Ltd.
    Artigo original: https://www.mql5.com/en/articles/13845

    Algoritmos de otimização populacional: simulação de têmpera isotrópica (Simulated Isotropic Annealing, SIA). Parte II Algoritmos de otimização populacional: simulação de têmpera isotrópica (Simulated Isotropic Annealing, SIA). Parte II
    A primeira parte do artigo foi dedicada ao conhecido e popular algoritmo de têmpera simulada, onde foram analisadas suas vantagens e descritos detalhadamente os pontos fracos. A segunda parte do artigo é dedicada a uma transformação radical do algoritmo, seu renascimento em um novo algoritmo de otimização, a simulação de têmpera isotrópica, SIA.
    Paradigmas de programação (Parte 1): Abordagem procedural para desenvolvimento de Expert Advisors com base na dinâmica de preços Paradigmas de programação (Parte 1): Abordagem procedural para desenvolvimento de Expert Advisors com base na dinâmica de preços
    Aprenda sobre paradigmas de programação e suas aplicações no código MQL5. Neste artigo, exploramos as características da programação procedural, além de oferecer exemplos práticos. Você aprenderá como desenvolver um Expert Advisor baseado na dinâmica de preços (Price Action), utilizando o indicador EMA e dados de velas. Além disso, o artigo apresenta o paradigma da programação funcional.
    Ciência de Dados e Aprendizado de Máquina (Parte 16): Uma nova perspectiva sobre árvores de decisão Ciência de Dados e Aprendizado de Máquina (Parte 16): Uma nova perspectiva sobre árvores de decisão
    Na última parte da nossa série sobre aprendizado de máquina e trabalho com big data, voltamos a falar sobre as árvores de decisão. Este artigo é destinado a traders que desejam entender o papel das árvores de decisão na análise de tendências de mercado. Aqui, reunimos todas as informações principais sobre a estrutura, o propósito e o uso dessas árvores. Vamos explorar as raízes e os ramos das árvores algorítmicas e descobrir como elas podem ser aplicadas na tomada de decisões de negociação. Vamos juntos dar um novo olhar às árvores de decisão e ver como elas podem ajudar a superar as dificuldades nos mercados financeiros.
    Buffers de cores em indicadores de vários símbolos e vários períodos Buffers de cores em indicadores de vários símbolos e vários períodos
    Neste artigo, analisaremos a estrutura do buffer de indicador em indicadores com vários símbolos e vários períodos e geraremos a exibição dos buffers coloridos desses indicadores no gráfico.