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

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

MetaTrader 5Testador | 26 junho 2024, 12:37
43 1
Kailash Bai Mina
Kailash Bai Mina

Introdução

Esta é a segunda parte da nossa série de artigos "EA de grid-hedge modificado em MQL5". Primeiro, vamos relembrar o que discutimos no artigo anterior. Na primeira parte, exploramos a estratégia clássica de hedge, automatizamos com um EA e realizamos testes no testador de estratégias, analisando alguns resultados iniciais. Este foi o primeiro passo na criação do EA modificado Grid-Hedge, que é uma combinação de estratégias clássicas de hedge e grade.

Mencionei anteriormente que a segunda parte seria dedicada à otimização da estratégia clássica de hedge. No entanto, devido a atrasos imprevistos, vamos focar por enquanto na estratégia clássica de grade.

Neste artigo, vamos nos aprofundar na estratégia clássica de grade, automatizá-la com um EA em MQL5 e realizar testes no testador de estratégias para analisar os resultados e obter informações úteis sobre a estratégia.

Então, vamos explorar os detalhes da estratégia clássica de grade.

Aqui está uma visão geral do que vamos cobrir neste artigo:

  1. Estratégia clássica de grade
  2. Automação da estratégia clássica de grade
  3. Teste da estratégia clássica de grade em dados históricos
  4. Conclusão


Estratégia clássica de grade 

Antes de prosseguir, vamos nos aprofundar na própria estratégia.

Começamos nossa abordagem com uma posição de compra, embora seja igualmente apropriado iniciar com uma posição de venda. Por enquanto, vamos nos concentrar no cenário de compra. Começamos com um preço inicial específico, usando um tamanho de lote pequeno para simplicidade, digamos, 0,01, o menor tamanho de lote possível. A partir dessa perspectiva, há dois resultados possíveis: o preço pode subir ou cair. Se o preço subir, obteremos lucro, cujo tamanho depende do aumento do preço. Em seguida, realizamos esse lucro assim que o preço atinge um nível predefinido de take-profit. O problema surge quando o preço começa a cair, e é aqui que nossa estratégia clássica de grade entra em ação, oferecendo uma garantia para assegurar o lucro.

Se o preço cair uma determinada quantidade (para simplificar, digamos 15 pontos), executaremos outra ordem de compra. A ordem é colocada no preço inicial menos 15 pontos, e aqui aumentamos o tamanho do lote, e para simplificar, dobramos, aplicando efetivamente um multiplicador de tamanho de lote igual a 2. Depois de colocar a nova ordem de compra, estamos novamente diante de dois cenários: o preço pode subir ou cair. O aumento do preço resultará em lucro a partir deste novo pedido devido ao aumento do tamanho do lote. Além disso, esse crescimento não apenas gera lucro, como também reduz as perdas no nosso pedido inicial de compra com tamanho de lote 0,01. O ponto em que nosso lucro líquido é igual a zero é quando o preço atinge o nível médio ponderado dos preços dos dois pedidos. Este valor médio ponderado pode ser calculado da seguinte maneira:

Neste cálculo, X_i representa o preço em que os pedidos de compra são colocados, e w_i denota os pesos correspondentes, determinados pelo multiplicador do tamanho do lote. Até este ponto, o nível médio ponderado dos preços W pode ser calculado assim:

W = 1 × (preço inicial) + 2 × (preço inicial - 15 pips) / 3

Aqui, X_1 é o preço inicial, X_2 é o preço inicial menos 15 pips, w_1 é igual a 1 e w_2 é igual a 2.

O ponto em que o lucro líquido atinge zero é quando o nível de preços se iguala ao nível médio ponderado calculado. A demonstração deste conceito é a seguinte:

Nota: Para simplicidade, ignoraremos os spreads por enquanto.

Vamos simplificar e assumir que o preço inicial é x, onde o pedido de compra é aberto com lote 0,01. Se, em seguida, o preço cair 15 pips, colocamos um novo pedido de compra no ponto x - 15 e duplicamos o tamanho do lote para 0,02. O nível médio ponderado dos preços neste ponto é calculado como (1 × x + 2 × (x - 15)) / 3, o que é igual a x - 10. Isso está 10 pips abaixo do nível de preço inicial.

Quando o preço atinge x - 15, o pedido inicial de compra de 0,01 tem uma perda de 1,5 USD (considerando a paridade EURUSD, onde 1 USD é equivalente a 10 pips). Se, em seguida, o preço subir até o nível médio ponderado x - 10, compensamos a perda de 0,5 USD no pedido inicial. Além disso, obtemos lucro do novo pedido de compra de 0,02, pois o preço subiu 5 pips do nível x - 15. Graças ao tamanho do lote dobrado, nosso ganho aqui também é duplicado e é de 1 USD. Assim, nosso lucro líquido total é de 0 USD (o que é -1,5 USD + 0,5 USD + 1 USD).

Se o preço subir a partir do nível médio ponderado x - 10, continuaremos a obter lucro até atingir o nível de take-profit.

Se o preço, em vez de subir, continuar a cair a partir do nível médio ponderado dos preços, aplicamos a mesma estratégia. Se o preço cair mais abaixo de x - 15 por 15 pips, um novo pedido de compra será colocado em x - 30, aumentando novamente o tamanho do lote em dobro em relação ao anterior, agora para 0,04 (2 vezes 0,02). O novo nível médio ponderado dos preços é calculado como (1 × x + 2 × (x - 15) + 4 × (x - 30)) / 7, simplificado para x - 21.428. Assim, o nível está localizado 21,428 pips abaixo do nível inicial de preços.

No nível de preço x - 30, o pedido inicial de compra de 0,01 ameaça uma perda de 3,0 USD (considerando a paridade EURUSD, onde 1 USD é equivalente a 10 pips). O segundo pedido de compra de 0,02 resulta em uma perda de 2 vezes 1,5 USD, o que corresponde a 3,0 USD, resultando em uma perda total de 6,0 USD. Однако, se o preço então subir até o nível médio ponderado x - 21,428, a perda no pedido inicial de compra 0,01 será parcialmente compensada em 0,8572 USD. Além disso, a perda no segundo pedido 0,02 será compensada em 2 vezes 0,8572 USD, o que equivale a 1,7144 USD. Além disso, obtemos lucro no novo pedido de compra 0,04, pois o preço subiu 8,572 pips do nível x - 30. Devido ao tamanho do lote aumentado em quatro vezes (0,04), nosso ganho também é aumentado em quatro vezes, sendo 0,8572 USD x 4 = 3,4288 USD. Assim, o lucro líquido total será aproximadamente 0 USD (-6 + 0,8572 + 1,7144 + 3,4288 = 0,0004 USD). Esse valor não é exatamente zero devido ao arredondamento ao calcular o nível médio ponderado dos preços.

Se o preço continuar a subir a partir do nível médio ponderado x - 21,428, continuaremos acumulando lucro até atingir o nível de take-profit. Por outro lado, se o preço cair novamente, repetimos o processo, dobrando o tamanho do lote a cada iteração e ajustando o nível médio ponderado do preço de acordo. Essa abordagem garante que sempre alcançaremos um lucro líquido de cerca de 0 USD cada vez que o preço atingir o nível médio ponderado dos preços, o que gradualmente leva a um lucro positivo.

O mesmo processo se aplica a ordens de venda. Neste caso, o ciclo começa com a ordem de venda inicial. Vamos analisar isso:

Começamos com uma posição de venda a um preço inicial específico, usando um tamanho de lote pequeno para simplicidade, digamos, 0,01 (o menor tamanho possível). A partir daí, dois resultados são possíveis: o preço pode subir ou descer. Se o preço cair, obteremos lucro dependendo da magnitude da queda e realizamos esse lucro quando o preço atinge nosso nível de take-profit. No entanto, a situação se complica quando o preço começa a subir, momento em que nossa estratégia clássica de grade é usada para garantir o lucro.

Suponha que o preço aumente uma certa quantidade, para simplificar, digamos, 15 pips. Em resposta, colocamos outra ordem de venda no preço inicial mais 15 pips e, para simplificar, dobramos o tamanho do lote, fazendo o multiplicador do tamanho do lote igual a 2. Após colocar a nova ordem de venda, o preço pode continuar subindo ou começar a cair. Se o preço cair, obteremos lucro a partir desta nova ordem de venda devido ao aumento do tamanho do lote. Além disso, essa queda não só gera lucro, mas também reduz a perda na nossa ordem inicial de venda com tamanho de lote 0,01. Alcançamos lucro líquido zero quando o preço atinge o nível médio ponderado dos preços das duas ordens — um conceito que já estudamos ao discutir as ordens de compra.

A fórmula de cálculo do valor médio ponderado permanece a mesma, considerando os preços e tamanhos dos lotes das ordens correspondentes. Essa abordagem estratégica garante que, mesmo em mercados voláteis, nossa posição permaneça protegida contra perdas, atingindo pontos de equilíbrio ou lucro conforme o mercado se move.

Para simplificar, suponha que o preço inicial seja x, onde a ordem de venda é colocada com tamanho de lote 0,01. Se o preço então subir 15 pips, colocamos uma nova ordem de venda no ponto x + 15, dobrando o tamanho do lote para 0,02. O nível médio ponderado dos preços neste ponto é calculado como (1 × x + 2 × (x + 15)) / 3, o que se simplifica para x + 10, ou 10 pips acima do nível inicial de preços.

Quando o preço atinge x + 15, a ordem inicial de venda de 0,01 terá uma perda de 1,5 USD (considerando a paridade EURUSD, onde 1 USD é equivalente a 10 pips). Se, em seguida, o preço cair até o nível médio ponderado x + 10, compensamos a perda de 0,5 USD na ordem inicial de venda. Além disso, obtemos lucro da nova ordem de venda de 0,02, pois o preço caiu 5 pips do nível x + 15. Devido ao tamanho do lote dobrado, nosso lucro também é dobrado, resultando em 1 USD. Portanto, nosso lucro líquido total é de 0 USD (calculado como -1,5 + 0,5 + 1).

Se o preço continuar a cair a partir do nível médio ponderado x + 10, continuaremos a obter lucro até atingir o nível de take-profit. Essa estratégia efetivamente equilibra perdas e ganhos em diferentes movimentos de preço, garantindo um cenário de lucro líquido ou ponto de equilíbrio em várias condições de mercado.

Se, em vez de cair, o preço continuar a subir a partir do nível médio ponderado dos preços, aplicamos a mesma estratégia de antes. Suponha que o preço suba mais 15 pips além do nível x + 15. Em resposta, colocamos uma nova ordem de venda em x + 30, novamente dobrando o tamanho do lote em relação ao anterior, agora para 0,04 (2 vezes 0,02). O nível médio ponderado revisado dos preços é então calculado como (1 × x + 2 × (x + 15) + 4 × (x + 30)) / 7, simplificado para x + 21,428 ou 21,428 pips acima do nível inicial de preços.

No nível de preço x + 30, a ordem inicial de venda de 0,01 terá uma perda de 3,0 USD (considerando a paridade EURUSD, onde 1 USD é equivalente a 10 pips). A segunda ordem de venda de 0,02 resultará em uma perda de 2 vezes 1,5 USD, o que equivale a 3,0 USD, resultando em uma perda total de 6,0 USD. No entanto, se o preço então cair até o nível médio ponderado x + 21,428, compensaremos parcialmente a perda na ordem inicial de venda de 0,01 em 0,8572 USD. Além disso, compensaremos a perda na segunda ordem de 0,02 em 2 vezes 0,8572 USD, o que equivale a 1,7144 USD. Além disso, obtemos lucro da nova ordem de venda de 0,04, pois o preço caiu 8,572 pips do nível x + 30. Devido ao tamanho do lote aumentado em quatro vezes (0,04), nosso ganho também é aumentado em quatro vezes, sendo 0,8572 USD x 4 = 3,4288 USD. Assim, o lucro líquido total será aproximadamente 0 USD (-6 + 0,8572 + 1,7144 + 3,4288 = 0,0004 USD). Esse valor não é exatamente zero devido ao arredondamento ao calcular o nível médio ponderado dos preços.

Se o preço continuar a cair a partir do nível médio ponderado x + 21,428, continuaremos a acumular lucro até atingir o nível de take-profit. Por outro lado, se o preço continuar a subir, repetimos o processo, dobrando o tamanho do lote a cada iteração e ajustando o nível médio ponderado do preço de acordo. Essa abordagem garante que sempre alcançaremos um lucro líquido de cerca de 0 USD cada vez que o preço atingir o nível médio ponderado dos preços, o que gradualmente leva a um lucro positivo.


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

Agora, vamos considerar a automatização desta estratégia clássica de grade.

Primeiro, declararemos algumas variáveis de entrada no espaço global:

input bool initialPositionBuy = true;
input double distance = 15;
input double takeProfit = 5;
input double initialLotSize = 0.01;
input double lotSizeMultiplier = 2;
  1. initialPositionBuy - variável lógica que determina o tipo de posição inicial - compra ou venda. Se true, a posição inicial será de compra, caso contrário, será de venda.
  2. distance - distância fixa entre os pedidos em pips.
  3. takeProfit - distância em pips acima do preço médio de todas as posições abertas, na qual todas as posições serão fechadas com lucro líquido total.
  4. initialLotSize - tamanho do lote da primeira posição.
  5. lotSizeMultiplier - multiplicador do tamanho do lote para cada posição.

Estas são as variáveis de entrada que vamos modificar para diferentes objetivos, como otimizar nossa estratégia. Agora, definiremos mais algumas variáveis no espaço global:

bool gridCycleRunning = false;
double lastPositionLotSize, lastPositionPrice, priceLevelsSumation, totalLotSizeSummation;

Essas variáveis serão usadas para os seguintes propósitos:

  1. gridCycleRunning - variável lógica que será true se o ciclo estiver em execução, e false caso contrário. Por padrão, é false.
  2. lastPositionLotSize - variável dupla criada para armazenar o tamanho do lote da última posição aberta em qualquer momento do ciclo.
  3. lastPositionPrice - variável dupla criada para armazenar o nível de preço da última posição em qualquer momento do ciclo.
  4. priceLevelsSumation - soma de todos os níveis de preços das posições abertas, que será usada no cálculo do nível médio ponderado dos preços.
  5. totalLotSizeSummation - soma de todos os tamanhos de lotes das posições, que será usada posteriormente no cálculo do nível médio ponderado dos preços.

Agora que definimos as variáveis de entrada principais, introduziremos uma função importante - StartGridCycle(), que lidará com a inicialização do ciclo.
//+------------------------------------------------------------------+
//| Hedge Cycle Intialization Function                               |
//+------------------------------------------------------------------+
void StartGridCycle()
   {
    double initialPrice = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);

    ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
    m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);

    lastPositionLotSize = initialLotSize;
    lastPositionPrice = initialPrice;
    ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10);
    ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed);

    priceLevelsSumation = initialLotSize * lastPositionPrice;
    totalLotSizeSummation = initialLotSize;

    if(m_trade.ResultRetcode() == 10009)
        gridCycleRunning = true;
   }
//+------------------------------------------------------------------+

Na função StartGridCycle(), primeiro criamos uma variável dupla InitialPrice, que armazenará o preço ask ou bid, dependendo da variável InitialPositionBuy. Em particular, se o parâmetro InitialPositionBuy for true, armazenamos o preço ask; se false, armazenamos o preço bid. Essa distinção é crucial, pois uma posição de compra só pode ser aberta ao preço ask, e uma posição de venda deve ser aberta ao preço bid.
ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
CTrade m_trade;
m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);

Agora, abriremos uma posição de compra ou venda, dependendo do valor de InitialPositionBuy. Para isso, criaremos a variável PositionType do tipo ENUM_ORDER_TYPE. ENUM_ORDER_TYPE é um tipo de variável pré-definido em MQL5, capaz de assumir valores inteiros de 0 a 8, cada um representando um tipo específico de pedido, conforme definido na tabela a seguir:

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

De fato, dentro de ENUM_ORDER_TYPE, os valores 0 e 1 correspondem a ORDER_TYPE_BUY e ORDER_TYPE_SELL, respectivamente. Para nossos propósitos, nos concentraremos nesses dois. Usar identificadores em vez de seus valores inteiros é vantajoso, pois os identificadores são mais intuitivos e fáceis de lembrar.

Portanto, definimos a variável PositionType como ORDER_TYPE_BUY se o parâmetro InitialPositionBuy for true; caso contrário, definimos como ORDER_TYPE_SELL.

Para continuar, primeiro importamos a biblioteca de negociação padrão Trade.mqh no espaço global. Isso é feito com a seguinte declaração:

#include <Trade/Trade.mqh>
CTrade m_trade;

Em seguida, definimos uma instância da classe CTrade, que chamamos de m_trade. Esta instância será usada para gerenciar operações de negociação. Para abrir uma posição, usamos a função PositionOpen da instância m_trade, que lida com a abertura real de posições de compra ou venda com base nos parâmetros que definimos, incluindo o tipo de posição e outras configurações de negociação relevantes.

m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, 0, 0);

Usando a função PositionOpen da instância m_trade, fornecemos vários parâmetros necessários para abrir a posição:

  1. _Symbol - o primeiro parâmetro _Symbol refere-se ao símbolo de negociação atual.

  2. positionType - o segundo parâmetro positionType que definimos anteriormente com base no valor de initialPositionBuy. Isso determina se a posição aberta é de compra ou venda.

  3. initialLotSize - o tamanho do lote para a posição, definido pela nossa variável de entrada initialLotSize.

  4. initialPrice - o preço ao qual a posição é aberta. Isso é determinado pela variável InitialPrice, que contém o preço ask ou bid dependendo do tipo de posição que estamos abrindo.

  5. SL (Stop Loss) e TP (Take Profit) - os dois últimos parâmetros são para definir o stop loss (SL) e o take profit (TP). Nesta estratégia específica, inicialmente não definimos esses parâmetros, pois o fechamento das ordens será determinado pela lógica da estratégia, especialmente quando o preço atingir o preço médio mais o valor do take profit.

Agora vamos para a próxima parte do código:

lastPositionLotSize = initialLotSize;
lastPositionPrice = initialPrice;
ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10);
ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed);

  1. Definindo as variáveis lastPositionLotSize e lastPositionPrice:

    • Inicializamos a variável lastPositionLotSize, definida no espaço global, igual a InitialLotSize. Essa variável é crucial, pois acompanha o tamanho do lote da última posição aberta, permitindo calcular o tamanho do lote para a próxima ordem multiplicando-o pelo multiplicador de entrada.
    • Da mesma forma, a variável lastPositionPrice também é definida como InitialPrice. Essa variável, também definida no espaço global, é necessária para determinar o nível de preço no qual as ordens subsequentes devem ser abertas.

  2. Criando uma linha horizontal para visualização:

    • Para melhorar a representação visual da nossa estratégia no gráfico, usamos a função ObjectCreate. Essa função recebe os seguintes parâmetros:
      • 0 para o gráfico atual
      • Next Position Price - nome do objeto
      • OBJ_HLINE - tipo de objeto de linha horizontal
      • Dois zeros adicionais, um para o subjanela e outro para a data e hora, pois a linha horizontal precisa apenas do nível de preço.
      • O nível de preço calculado para a próxima ordem (lastPositionPrice - distance * _Point * 10) como o último parâmetro.
    • Para definir a cor vermelha desta linha horizontal, usamos a função ObjectSetInteger com a propriedade OBJPROP_COLOR definida como clrRed.

  3. Transição para o gerenciamento do preço médio no ciclo de grade:

    • Depois de concluir essas etapas iniciais, passamos para o gerenciamento do preço médio dentro do ciclo de grade. Isso envolve o cálculo dinâmico e o ajuste do preço médio à medida que novas posições são abertas e o mercado se desenvolve, o que é um componente chave da estratégia de negociação em grade.

Vamos para a próxima parte da implementação do código.

priceLevelsSumation = initialLotSize * lastPositionPrice;
totalLotSizeSummation = initialLotSize;

  1. Definindo priceLevelsSumation:

    • Definimos priceLevelsSumation no espaço global para calcular o preço médio ponderado de todas as posições abertas. Inicialmente, como há apenas uma ordem, definimos priceLevelsSumation como o valor de lastPositionPrice multiplicado pelo peso correspondente, que é o tamanho do lote da ordem. Essa configuração prepara a variável para acumular mais níveis de preços, cada um multiplicado pelo tamanho do lote correspondente à medida que novas posições são abertas.
  2. Inicializando totalLotSizeSummation:

    • A variável totalLotSizeSummation é inicialmente definida como initialLotSize. Isso faz sentido no contexto da fórmula do valor médio ponderado, onde precisamos dividir pelo peso total. No início, com apenas uma ordem, o peso total é o tamanho do lote dessa ordem. À medida que abrimos mais posições, adicionamos os pesos (tamanhos dos lotes) a essa soma, atualizando dinamicamente o peso total.

Agora vamos para a última parte da função StartGridCycle():

if(m_trade.ResultRetcode() == 10009)
    gridCycleRunning = true;

Configurando hedgeCycleRunning:
  • A variável hedgeCycleRunning é definida como true apenas após a abertura bem-sucedida de uma posição. Isso é verificado usando a função ResultRetcode() da instância CTrade chamada trade. O código de retorno "10009" indica que a ordem foi colocada com sucesso. (Nota: diferentes códigos de retorno podem ser usados para diferentes resultados de solicitações de negociação.)
  • Usar hedgeCycleRunning é crucial para a estratégia, pois sinaliza o início do ciclo de grade. O valor desse indicador se tornará mais claro nas próximas partes do código.

Concluindo a função StartGridCycle(), que inicia a estratégia de grade, passamos para a função OnTick(). Dividimos a função em 5 segmentos, cada um responsável por um aspecto específico da lógica de negociação:

  1. Início do ciclo de grade
  2. Processamento de posições de compra
  3. Processamento de posições de venda
  4. Função de fechamento do ciclo de grade
  5. Processamento do fechamento de posições
  1. Início do ciclo de grade:
    double price = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
      
      if(!gridCycleRunning)
         {
          StartGridCycle();
         }
    1. Declaração da variável de preço:

      • Uma nova variável chamada Price foi declarada. Seu valor é determinado pelo indicador InitialPositionBuy:
        • Se o parâmetro InitialPositionBuy for true, o preço é definido como o preço ASK atual.
        • Se o parâmetro InitialPositionBuy for false, o preço é definido como o preço BID atual.
    2. Execução condicional baseada em gridCycleRunning:

      • O próximo passo envolve a verificação condicional da variável gridCycleRunning:
        • Se GridCycleRunning for false, isso significa que o ciclo de grade ainda não começou ou concluiu o ciclo anterior. Nesse caso, executamos a função StartGridCycle(), que foi explicada detalhadamente anteriormente. Esta função inicializa o ciclo de grade, abrindo a primeira posição e configurando os parâmetros necessários.
        • Se GridCycleRunning for true, isso significa que o ciclo de grade já está ativo. Nesse caso, prefiro não fazer nada por enquanto. Essa decisão permite que o ciclo de grade existente continue funcionando com base na lógica estabelecida, sem iniciar um novo ciclo ou interferir no atual.

    Essa abordagem gerencia efetivamente o início e a continuidade do ciclo de grade, garantindo que a estratégia de negociação siga o fluxo de trabalho planejado. Vamos prosseguir para os próximos passos da implementação da estratégia de grade.


  2. Processamento de posições de compra:
    if(initialPositionBuy && price <= lastPositionPrice - distance * _Point * 10 && gridCycleRunning)
         {
          double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
          m_trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, price, 0, 0);
      
          lastPositionLotSize *= lotSizeMultiplier;
          lastPositionPrice = price;
          ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10);
          ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed);
      
          priceLevelsSumation += newPositionLotSize * lastPositionPrice;
          totalLotSizeSummation += newPositionLotSize;
          ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation);
          ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen);
         }

    Aqui temos outra instrução if, que possui 3 condições:

    1. Condição initialPositionBuy:

      • Este segmento de código é executado apenas se a variável InitialPositionBuy for true. Essa condição garante que a lógica de processamento de posições de compra seja separada da lógica de processamento de posições de venda.

    2. Condição de preço:

      • A segunda condição verifica se a variável Price é menor ou igual a lastPositionPrice - distance * _Point * 10. Essa condição é crucial para determinar o momento de abrir uma nova posição de compra. Aqui, usamos a operação de subtração (-), alinhada com a lógica direcional definida em nossa estratégia de grade para posições de compra.

    3. Condição gridCycleRunning:

      • A terceira condição requer que a variável gridCycleRunning seja true, indicando que o ciclo de grade está ativo no momento. Isso é importante para garantir que novas posições sejam abertas apenas dentro do ciclo de negociação atual.

    Se todas as três condições forem atendidas, o EA prossegue para abrir uma nova posição de compra. No entanto, antes disso, ele calcula o tamanho do lote para a nova posição:

    • Uma nova variável dupla newPositionLotSize é declarada e definida como lastPositionLotSize multiplicada por lotSizeMultiplier.
    • O tamanho do lote resultante é então normalizado para duas casas decimais para ser válido, pois o tamanho do lote deve ser um múltiplo de 0,01.

    Essa abordagem garante a abertura de novas posições com tamanhos adequados, conforme as regras da estratégia de grade. Posteriormente, o EA usa a função PositionOpen() da instância da classe CTrade chamada m_trade (declarada anteriormente no espaço global) para abrir a posição de compra com o tamanho do lote calculado.

    Continuando com a lógica, o próximo passo será atualizar a variável lastPositionLotSize. Isso é crucial para manter a precisão dos tamanhos dos lotes em pedidos subsequentes:

    • LastPositionLotSize é definido como igual a si mesmo multiplicado por lotSizeMultiplier. É importante notar que essa multiplicação não requer normalização para duas casas decimais.
    • A razão para evitar a normalização é ilustrada pelo exemplo em que lotSizeMultiplier é 1,5 e initialLotSize é 0,01. Multiplicar 0,01 por 1,5 resulta em 0,015. Esse valor, quando normalizado para duas casas decimais, é arredondado de volta para 0,01. Isso cria um ciclo em que o tamanho do lote permanece constante em 0,01, apesar da multiplicação.
    • Para evitar esse problema e garantir que os tamanhos dos lotes aumentem corretamente, lastPositionLotSize é atualizado usando o produto não normalizado de si mesmo e lotSizeMultiplier. Esse passo é crucial para o funcionamento correto da estratégia de grade, especialmente com multiplicadores fracionários.

    Com essa atualização, o EA rastreia e ajusta com precisão os tamanhos dos lotes para novas posições, mantendo o desenvolvimento planejado da estratégia de grade.

    Continuando o processo, o próximo passo envolve atualizar lastPositionPrice e visualizar o próximo nível de posição no gráfico:

    1. Atualização de lastPositionPrice:
      • A variável LastPositionPrice é atualizada e se torna igual ao preço, que é determinado pela condição InitialPositionBuy. Como a primeira condição do if garante entrada apenas se o parâmetro InitialPositionBuy for true, nesse contexto, o preço será o preço ASK.

    2. Visualização do próximo nível de posição:
      • Uma linha horizontal chamada Next Position Price (preço da próxima posição) é desenhada no gráfico. Essa linha representa o nível de preço em que o próximo pedido será aberto.
      • Como o parâmetro InitialPositionBuy é true, indicando que a próxima posição será de compra, o nível de preço para essa linha é definido como LastPositionPrice (que foi atualizado recentemente) menos a distância (como especificado na entrada), multiplicado por _Point e depois multiplicado por 10.
      • Essa linha horizontal é criada usando a função ObjectCreate(), e sua cor é definida como clrRed usando funções adicionais de propriedades de objeto para facilitar a visualização. Esse auxílio visual ajuda a identificar facilmente o nível de preço para o próximo pedido potencial de compra.

    Atualizando lastPositionPrice e visualmente marcando o próximo nível de pedido, o EA se prepara efetivamente para os próximos passos do ciclo de grade, garantindo que cada nova posição atenda aos critérios da estratégia e seja rastreada visualmente no gráfico.

    Agora, vamos esclarecer o cálculo do nível médio de preços, com foco especial no valor médio ponderado:

    1. Atualização de priceLevelsSumation e totalLotSizeSummation:

      • Para atualizar o preço médio ponderado, adicionamos o produto de LastPositionPrice e newPositionLotSize a PriceLevelsSumation.
      • Para totalLotSizeSummation, simplesmente adicionamos o valor de newPositionLotSize.
      • Essas atualizações são cruciais para rastrear os níveis de preço acumulados e os tamanhos totais dos lotes de todas as posições.

    2. Cálculo do nível médio ponderado de preços:

      • O nível médio de preços, que neste contexto é o nível médio ponderado, é calculado dividindo priceLevelsSumation por totalLotSizeSummation.
      • Esse cálculo reflete com precisão a média dos preços de todas as posições abertas, considerando os tamanhos dos lotes correspondentes.

    3. Visualização do nível médio ponderado de preços:

      • No gráfico, é criada outra linha horizontal no nível médio de preço calculado usando a função ObjectCreate().
      • A cor dessa linha é definida como clrGreen, o que a distingue da outra linha horizontal que indica o preço da próxima posição.
      • É importante notar que este cálculo utiliza o valor normalizado de LastPositionLotSize multiplicado por lotSizeMultiplier. Isso garante que o tamanho real do lote da nova posição aberta seja considerado, proporcionando um valor médio ponderado preciso.

    Com essas etapas, o EA não apenas rastreia o nível médio dos preços de todas as posições abertas, mas também o representa visualmente no gráfico. Isso facilita o controle e a tomada de decisões com base no estado atual do ciclo de grade e nas condições do mercado.


  3. Processamento de posições de venda:
    if(!initialPositionBuy && price >= lastPositionPrice + distance * _Point * 10 && gridCycleRunning)
         {
          double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2);
          m_trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, newPositionLotSize, price, 0, 0);
      
          lastPositionLotSize *= lotSizeMultiplier;
          lastPositionPrice = price;
          ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice + distance * _Point * 10);
          ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed);
      
          priceLevelsSumation += newPositionLotSize * lastPositionPrice;
          totalLotSizeSummation += newPositionLotSize;
          ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation);
          ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen);
         }

    Aqui temos outra instrução if, que possui 3 condições:

    1. Condição initialPositionBuy:

      • Este segmento de código é executado apenas se o parâmetro InitialPositionBuy for false. Essa condição garante que a lógica processe posições de venda separadamente das posições de compra.

    2. Condição de preço:

      • A segunda condição verifica se o preço é maior ou igual a lastPositionPrice + distance * _Point * 10. Isso é crucial para determinar o momento apropriado para abrir uma nova posição de venda. Neste caso, usamos a operação de adição (+), alinhada com a lógica direcional de nossa estratégia para posições de venda.

    3. Condição gridCycleRunning:

      • A terceira condição requer que GridCycleRunning seja true, indicando que o ciclo de grade está ativo. Isso garante que novas posições sejam abertas apenas dentro do ciclo de negociação atual.

    Se todas as três condições forem atendidas, o EA prossegue para abrir uma nova posição de venda:

    • Uma variável dupla newPositionLotSize é declarada e definida como lastPositionLotSize multiplicada por lotSizeMultiplier.
    • O novo tamanho do lote é então normalizado para duas casas decimais para ser válido, pois o tamanho do lote deve ser um múltiplo de 0,01.
    • Finalmente, o EA usa a função PositionOpen() da instância da classe CTrade chamada m_trade (declarada anteriormente no espaço global) para abrir a posição de venda com o tamanho do lote calculado.

    Essa abordagem garante que novas posições de venda sejam abertas em intervalos apropriados e em tamanhos adequados, seguindo as regras da estratégia de grade e mantendo o desenvolvimento da estratégia.

    No próximo estágio de processamento de posições de venda, focaremos na atualização correta da variável lastPositionLotSize:

    • A variável lastPositionLotSize é atualizada para ser igual a si mesma multiplicada por lotSizeMultiplier. Esse passo é crucial para manter o progresso dos tamanhos dos lotes no ciclo de grade.
    • É importante notar que essa multiplicação não requer normalização para duas casas decimais. Essa decisão é vital para evitar problemas potenciais com multiplicadores fracionários.
    • Para ilustrar - com lotSizeMultiplier igual a 1,5 e initialLotSize igual a 0,01, o resultado da multiplicação será 0,015. A normalização desse valor para duas casas decimais arredondará de volta para 0,01. Repetir esse processo resultará constantemente em um tamanho de lote de 0,01, criando um ciclo que contraria o objetivo da estratégia.
    • Para contornar esse problema, lastPositionLotSize é definido como o produto não normalizado de si mesmo e lotSizeMultiplier. Essa abordagem garante o aumento adequado dos tamanhos dos lotes, especialmente ao lidar com multiplicadores que resultam em tamanhos de lotes fracionários.

    Atualizando lastPositionLotSize sem normalização, o EA rastreia e ajusta efetivamente os tamanhos dos lotes para novas posições de venda, garantindo que a estratégia de grade funcione corretamente.

    1. Atualização de lastPositionPrice:

      • A variável lastPositionPrice é atualizada para ser igual ao preço, que será o preço ASK ou BID dependendo do valor de InitialPositionBuy.
      • Neste caso, como estamos entrando nesse segmento de código apenas se o parâmetro InitialPositionBuy for false (conforme a primeira condição), lastPositionPrice é definido como o preço BID.

    2. Exibição da linha horizontal para a próxima posição:

      • Uma linha horizontal chamada Next Position Price (preço da próxima posição) é desenhada no gráfico. Essa linha indica o nível de preço em que a próxima ordem será aberta (neste caso, uma venda).
      • O nível de preço para essa linha horizontal é definido como lastPositionPrice (que foi atualizado) mais a distância especificada nas entradas, multiplicado por _Point e, em seguida, multiplicado por 10. Esse cálculo determina o nível adequado para a próxima ordem de venda.
      • A linha é criada usando a função ObjectCreate() — uma função predefinida no MQL5 para desenhar objetos nos gráficos.
      • A cor dessa linha é definida como clrRed, o que melhora sua visibilidade e facilita a distinção no gráfico.

    Configurando corretamente lastPositionPrice e representando visualmente o nível da próxima ordem, o EA se prepara efetivamente para ordens subsequentes de venda, garantindo que elas sigam as regras da estratégia de grade e sejam facilmente identificáveis no gráfico.

    Ao calcular o nível médio dos preços e, especialmente, ao considerar o valor médio ponderado para posições de venda, esse processo inclui:

    1. Atualização de priceLevelsSumation e totalLotSizeSummation:

      • O valor de lastPositionPrice multiplicado por newPositionLotSize é adicionado a priceLevelsSummation. Nesse ponto, os níveis de preços de todas as posições abertas são acumulados, cada um ponderado pelo tamanho do lote correspondente.
      • O valor de newPositionLotSize é adicionado a totalLotSizeSummation. Essa variável rastreia os tamanhos acumulados dos lotes de todas as posições.

    2. Cálculo do nível médio ponderado de preços:

      • O nível médio de preços é obtido dividindo PriceLevelsSummation por totalLotSizeSummation. Esse cálculo fornece o preço médio ponderado de todas as posições abertas.

    3. Visualização do nível médio ponderado de preços:

      • Uma linha horizontal é criada no nível do preço médio ponderado usando a função ObjectCreate(). Essa representação visual ajuda a rastrear o preço médio de todas as posições.
      • A cor dessa linha é definida como clrGreen, facilitando sua distinção de outras linhas no gráfico.
      • É importante notar que esses cálculos utilizam o valor normalizado de lastPositionLotSize, multiplicado por lotSizeMultiplier. Isso garante que os tamanhos reais dos lotes das posições abertas sejam considerados, proporcionando um cálculo preciso do valor médio ponderado.

    Esse método de cálculo e visualização do nível médio ponderado de preços é crucial para o gerenciamento eficaz das posições de venda no ciclo de grade, permitindo decisões informadas com base no estado atual do mercado e das posições.


  4. Função de fechamento do ciclo de grade:
    //+------------------------------------------------------------------+
    //| Stop Function for a particular Grid Cycle                        |
    //+------------------------------------------------------------------+
    void StopGridCycle()
       {
        gridCycleRunning = false;
        ObjectDelete(0, "Next Position Price");
        ObjectDelete(0, "Average Price");
        for(int i = PositionsTotal() - 1; i >= 0; i--)
           {
            ulong ticket = PositionGetTicket(i);
            if(PositionSelectByTicket(ticket))
               {
                m_trade.PositionClose(ticket);
               }
           }
       }
    //+------------------------------------------------------------------+

    1. Configuração de gridCycleRunning para False:

      • A primeira ação nessa função é definir a variável lógica gridCycleRunning como false. Essa mudança indica que o ciclo de grade não está mais ativo e está em processo de encerramento.

    2. Remoção de objetos do gráfico:

      • Usamos a função predefinida ObjectDelete() para remover dois objetos específicos do gráfico - Next Position Price (preço da próxima posição) e Average Price (preço médio). Essa etapa limpa o gráfico dos marcadores, indicando que o ciclo está terminando e esses níveis de preço não são mais relevantes.

    3. Fechamento de todas as posições:

      • Em seguida, a função passa a iterar por todas as posições abertas.
      • Cada posição é selecionada individualmente pelo número do ticket.
      • Assim que uma posição é selecionada, ela é fechada usando a função PositionClose() da instância m_trade. O número do ticket da posição é passado como parâmetro para essa função.
      • Essa abordagem sistemática garante que todas as posições abertas dentro do ciclo de grade sejam fechadas, encerrando efetivamente a atividade de negociação para esse ciclo específico.

    Executando essas etapas, a função de fechamento do ciclo de grade encerra metodicamente todas as posições, redefine o ambiente de negociação e prepara o EA para um novo ciclo de grade.


  5. Processamento do fechamento de posições:

    if(gridCycleRunning)
       {
        if(initialPositionBuy && price >= (priceLevelsSumation / totalLotSizeSummation) + takeProfit * _Point * 10)
            StopGridCycle();
    
        if(!initialPositionBuy && price <= (priceLevelsSumation / totalLotSizeSummation) - takeProfit * _Point * 10)
            StopGridCycle();
       }

    Na seção final "Processamento do fechamento de posições", o processo de fechamento de ordens é controlado por uma instrução if, dependente de certas condições:

    1. A condição principal para a execução de qualquer ação é que o valor de gridCycleRunning deve ser true, indicando um ciclo de grade ativo.
    2. Dentro dessa condição, há mais duas verificações condicionais baseadas no valor de initialPositionBuy:
      • Se o parâmetro initialPositionBuy for true e o preço atual exceder o preço médio ponderado pelo número de pips especificado como takeProfit (conforme entrada das variáveis), então a função StopGridCycle() é executada.
      • Por outro lado, se o parâmetro initialPositionBuy for false e o preço atual estiver abaixo do preço médio ponderado pelo número de pips especificado como takeProfit, então a função StopGridCycle() é executada.

    Essas condições garantem a parada do ciclo de grade e o fechamento das posições com base nos critérios de take-profit especificados em relação ao nível médio ponderado de preços.


Teste da estratégia clássica de grade em dados históricos

A automação da nossa estratégia clássica de grade está concluída. É hora de avaliar sua eficácia em condições reais.

Para o teste em dados históricos, serão utilizados os seguintes parâmetros de entrada:

  • initialPositionBuy: true
  • distance: 15 pips
  • takeProfit: 5 pips
  • initialLotSize: 0,01
  • lotSizeMultiplier: 2,0

O teste será realizado no par EURUSD de 1º de novembro a 22 de dezembro de 2023. A alavancagem escolhida é 1:500, com um depósito inicial de 10.000 USD. Quanto ao timeframe, ele não tem relevância para nossa estratégia, então qualquer um serve, pois não influencia o resultado. Este teste, embora cubra um período relativamente curto de menos de dois meses, deve servir como uma amostra representativa para os resultados potenciais ao longo de um período mais longo.

Vamos ver os resultados:


Os resultados são bastante bons. Analisando os resultados do teste histórico, observamos tendências interessantes representadas pelas linhas azuis e verdes no gráfico. Vamos entender o que cada linha significa e como seus movimentos refletem a eficácia da estratégia:

  1. Linha azul e linha verde:

    • A linha azul representa o saldo da conta, e a linha verde, o patrimônio (equity).
    • Observa-se uma tendência notável: o saldo aumenta, o patrimônio diminui e, eventualmente, eles convergem em um ponto.

  2. Explicação das flutuações do saldo:

    • A estratégia requer o fechamento simultâneo de todas as ordens do ciclo, o que idealmente deve levar ao aumento do saldo. No entanto, o gráfico mostra um padrão de alta e queda no saldo, que precisa de mais explicações.
    • Essa flutuação é explicada por um pequeno atraso (frações de segundo) no fechamento das ordens. Embora as ordens sejam fechadas quase simultaneamente, o gráfico registra atualizações graduais do saldo.
    • Inicialmente, o saldo aumenta, pois as posições lucrativas são fechadas primeiro. As posições são fechadas ciclicamente, começando pela última (mais lucrativa) posição, como indicado pela função PositionsTotal(). Portanto, o movimento ascendente e a breve queda da linha de saldo podem ser ignorados, focando-se em vez disso na tendência geral de alta.

  3. Movimento da linha de patrimônio:

    • De acordo com o saldo, o patrimônio primeiro cai e depois sobe. Isso ocorre porque as posições lucrativas são fechadas primeiro, reduzindo temporariamente o patrimônio.
    • O movimento da linha verde do patrimônio segue a mesma lógica da linha azul do saldo, levando à mesma conclusão de uma tendência geral positiva.

Assim, apesar das pequenas flutuações refletidas no gráfico devido à sequência de fechamento das ordens e pequenos atrasos, a tendência geral indica um resultado bem-sucedido da estratégia, conforme evidenciado pela convergência final e movimento ascendente das linhas de saldo e patrimônio.

Os resultados do teste histórico demonstram a lucratividade da estratégia clássica de grade. No entanto, é importante reconhecer uma limitação significativa inerente a essa estratégia: a necessidade de manter posições por longos períodos.

A estratégia muitas vezes exige capital significativo para resistir às quedas antes de se tornar lucrativa. A capacidade de manter posições em condições de movimentos adversos do mercado, sem enfrentar chamadas de margem e a necessidade de fechar posições com prejuízo, é crucial.

Eliminar essa limitação será um ponto-chave nas partes subsequentes da nossa série, onde exploraremos métodos de otimização da estratégia. O objetivo será aumentar a eficácia da estratégia e reduzir a necessidade de manter posições por longos períodos, tornando a estratégia mais acessível e menos arriscada para traders com diferentes tamanhos de capital.


Conclusão

Nesta edição, aprofundamos as nuances da estratégia clássica de grade e a automatizamos com sucesso usando um EA em MQL5. Também analisamos alguns resultados iniciais dessa estratégia, destacando seu potencial e áreas para melhorias.

No entanto, nosso trabalho de otimização dessas estratégias está longe de terminar. As próximas partes da série serão dedicadas ao refinamento da estratégia, em particular ao ajuste dos valores ótimos para os parâmetros de entrada, como distance, takeProfit, initialLotSize e lotSizeMultiplier, para maximizar os lucros e minimizar as quedas. Um aspecto interessante da nossa pesquisa futura será determinar a eficácia da direção da posição inicial (compra ou venda), que pode variar dependendo das diferentes moedas e condições de mercado.

Há muito mais por vir em nossos próximos artigos. Revelaremos ideias e métodos ainda mais valiosos. Obrigado pela atenção e espero que meus artigos tenham sido úteis para você. Se você tiver tópicos ou ideias que gostaria de discutir na próxima parte desta série, suas sugestões são sempre bem-vindas.

Boa programação! Boas negociações!


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

Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Luciano Cabral Rola Neto
Luciano Cabral Rola Neto | 26 jun 2024 em 13:15
Very nice, I implemented an EA using grid strategy, but not multiplying the lots, I sum the lots for next positions, and the trigger to open a position I used Bollinger Bands indicator.. My EA is Rola Scalper, free in market. I’m using it in real account since january with good profit.
Redes neurais de maneira fácil (Parte 72): previsão de trajetórias em condições de ruído Redes neurais de maneira fácil (Parte 72): previsão de trajetórias em condições de ruído
A qualidade da previsão de estados futuros desempenha um papel importante no método Goal-Conditioned Predictive Coding, com o qual nos familiarizamos no artigo anterior. Neste artigo, quero apresentar a vocês um algoritmo capaz de aumentar significativamente a qualidade da previsão em ambientes estocásticos, que incluem os mercados financeiros.
Desenvolvendo um sistema de Replay (Parte 54): O nascimento do primeiro módulo Desenvolvendo um sistema de Replay (Parte 54): O nascimento do primeiro módulo
Neste artigo, iremos ver como construir o primeiro dos módulos, realmente funcional a fim de ser utilizado no sistema de replay / simulador. Além de ter como proposito geral servir para outras coisas também. O módulo que será construído aqui será o do indicador de mouse.
Previsão baseada em aprendizado profundo e abertura de ordens com o pacote MetaTrader 5 python e arquivo de modelo ONNX Previsão baseada em aprendizado profundo e abertura de ordens com o pacote MetaTrader 5 python e arquivo de modelo ONNX
O projeto envolve o uso de Python para previsão em mercados financeiros baseada em aprendizado profundo. Nós exploraremos as nuances do teste de desempenho do modelo usando indicadores-chave como erro absoluto médio (MAE), erro quadrático médio (MSE) e R-quadrado (R2), além de aprender a integrar tudo isso em um arquivo executável. Também criaremos um arquivo de modelo ONNX e um EA (Expert Advisor).
Anotação de dados na análise de série temporal (Parte 6): Aplicação e teste de EA com ONNX Anotação de dados na análise de série temporal (Parte 6): Aplicação e teste de EA com ONNX
Nesta série de artigos, apresentamos vários métodos de anotação de séries temporais, que podem criar dados adequados à maioria dos modelos de inteligência artificial (IA). A anotação de dados direcionada pode tornar o modelo de IA treinado mais alinhado aos objetivos e tarefas do usuário, aumentar a precisão do modelo e até ajudar o modelo a alcançar um salto qualitativo!