Automatizando Estratégias de Trading em MQL5 (Parte 2): O Sistema Kumo Breakout com Ichimoku e Awesome Oscillator
Introdução
No artigo anterior (Parte 1 da série), demonstramos como automatizar o Sistema Profitunity (Trading Chaos de Bill Williams). Neste artigo (Parte 2), demonstramos como transformar a Estratégia Kumo Breakout em um Expert Advisor (EA) totalmente funcional em MetaQuotes Language 5 (MQL5). A Estratégia Kumo Breakout utiliza o indicador Ichimoku Kinko Hyo para identificar potenciais reversões de mercado e continuações de tendência, com movimentos de preço em relação ao Kumo (nuvem) — uma zona dinâmica de suporte e resistência formada pelas linhas Senkou Span A e Senkou Span B. Ao incorporar o indicador Awesome Oscillator como ferramenta de confirmação de tendência, podemos filtrar sinais falsos e aumentar a precisão das entradas e saídas de trades. Esta estratégia é amplamente utilizada por traders que buscam capitalizar movimentos de mercado impulsionados por forte momentum.
Percorremos o processo de codificação da lógica da estratégia, gerenciamento de trades e aprimoramento do controle de risco com trailing stops. Ao final deste artigo, você terá uma compreensão clara de como automatizar a estratégia, testar seu desempenho utilizando o Strategy Tester do MQL5 e refiná-la para obter resultados ideais. Dividimos o processo nas seções a seguir para facilitar o entendimento.
- Visão Geral da Estratégia Kumo Breakout
- Implementação da Estratégia Kumo Breakout em MQL5
- Testes e Otimização da Estratégia
- Conclusão
Visão Geral da Estratégia Kumo Breakout
A Estratégia Kumo Breakout é uma abordagem seguidora de tendência que busca aproveitar movimentos de preço além dos limites da nuvem Kumo. O Kumo, também chamado de nuvem Kumo, é uma área sombreada entre as linhas Senkou Span A e Senkou Span B do indicador Ichimoku Kinko Hyo, que atua como níveis dinâmicos de suporte e resistência. Quando o preço rompe acima do Kumo, isso sinaliza uma possível tendência de alta, enquanto um rompimento abaixo indica uma possível tendência de baixa. Quanto ao indicador, os parâmetros utilizados em sua configuração são Tenkan-sen = 8, Kijun-sen = 29 e Senkou-span B = 34. Aqui estão as configurações:

Para filtrar sinais falsos, a estratégia também integra o indicador Awesome Oscillator para fornecer confirmação adicional para as entradas de trades. O Awesome Oscillator identifica mudanças de momentum medindo a diferença entre uma média móvel simples de 34 períodos e uma de 5 períodos, plotadas sobre o preço mediano. Sinais de compra são validados quando o oscilador cruza de negativo para positivo, e sinais de venda são confirmados quando cruza de positivo para negativo. Ao combinar rompimentos do Kumo com confirmação de momentum do Awesome Oscillator, a estratégia busca reduzir sinais falsos e aumentar a probabilidade de trades bem-sucedidos.
Quando totalmente combinada, ela representa o que é mostrado abaixo no gráfico.

Para sair das posições, usamos a lógica de mudanças de momentum. Quando o oscilador cruza de positivo para negativo, isso indica uma mudança no momentum de alta e encerramos as posições de compra existentes. Da mesma forma, quando o oscilador cruza de negativo para positivo, encerramos as posições de venda existentes. Aqui está uma ilustração.

Essa abordagem é particularmente eficaz em mercados em tendência, onde o momentum é forte. No entanto, durante períodos de consolidação, a estratégia pode gerar sinais falsos devido à natureza irregular da ação do preço dentro do Kumo e do oscilador. Como resultado, podemos aplicar filtros adicionais ou técnicas de gerenciamento de risco, como trailing stops, para mitigar possíveis rebaixamentos. Compreender esses princípios fundamentais é essencial para implementar a estratégia com sucesso como um Expert Advisor automatizado.
Implementação da Estratégia Kumo Breakout em MQL5
Após aprender todas as teorias sobre a estratégia de trading Kumo Breakout, vamos então automatizar a teoria e desenvolver um Expert Advisor (EA) em MetaQuotes Language 5 (MQL5) para o MetaTrader 5.
Para criar um Expert Advisor (EA), no seu terminal MetaTrader 5, clique na aba Ferramentas e selecione MetaQuotes Language Editor, ou simplesmente pressione F4 no teclado. Alternativamente, você pode clicar no ícone IDE (Integrated Development Environment) na barra de ferramentas. Isso abrirá o MetaQuotes Language Editor, que permite escrever robôs de trading, indicadores técnicos, scripts e bibliotecas de funções. Quando o MetaEditor estiver aberto, na barra de ferramentas, navegue até a aba Arquivo e selecione Novo Arquivo, ou simplesmente pressione CTRL + N para criar um novo documento. Alternativamente, você pode clicar no ícone Novo na aba de ferramentas. Isso resultará em uma janela pop-up do MQL Wizard.
No Wizard que aparecer, selecione Expert Advisor (template) e clique em Avançar. Nas propriedades gerais do Expert Advisor, na seção de nome, forneça o nome do arquivo do seu expert. Observe que, para especificar ou criar uma pasta caso ela não exista, você utiliza a barra invertida antes do nome do EA. Por exemplo, aqui temos “Experts\” por padrão. Isso significa que nosso EA será criado dentro da pasta Experts e poderá ser encontrado lá. As demais seções são bastante simples, mas você pode seguir o link na parte inferior do Wizard para saber exatamente como realizar o processo.

Após fornecer o nome desejado para o arquivo do Expert Advisor, clique em Avançar, clique novamente em Avançar e depois em Concluir. Após fazer tudo isso, estamos prontos para codificar e programar nossa estratégia.
Primeiro, começamos definindo alguns metadados sobre o Expert Advisor (EA). Isso inclui o nome do EA, informações de copyright e um link para o site da MetaQuotes. Também especificamos a versão do EA, definida como “1.00”.
//+------------------------------------------------------------------+ //| 1. Kumo Breakout EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00"
Isso exibirá os metadados do sistema ao carregar o programa. Podemos então prosseguir adicionando algumas variáveis globais que usaremos dentro do programa. Primeiro, incluímos uma instância de trade usando #include no início do código-fonte. Isso nos dá acesso à “classe CTrade”, que utilizaremos para criar um objeto de trade. Isso é crucial, pois precisamos dele para abrir operações.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os colchetes angulares indicam que o arquivo Trade.mqh será obtido do diretório padrão (normalmente é o diretório_de_instalação_do_terminal\MQL5\Include). O diretório atual não é incluído na busca. A linha pode ser colocada em qualquer parte do programa, mas, geralmente, todas as inclusões são colocadas no início do código-fonte, para melhor estrutura e facilidade de referência. A declaração do objeto obj_Trade da classe CTrade nos dará acesso aos métodos contidos nessa classe com facilidade, graças aos desenvolvedores do MQL5.

Depois disso, precisamos declarar vários identificadores importantes de indicadores que usaremos no sistema de trading.
int handle_Kumo = INVALID_HANDLE; //--- Initialize the Kumo indicator handle to an invalid state int handle_AO = INVALID_HANDLE; //--- Initialize the Awesome Oscillator handle to an invalid state
Aqui, declaramos duas variáveis do tipo integer, “handle_Kumo” e “handle_AO”, que usamos para armazenar os identificadores do indicador Kumo (Ichimoku) e do indicador Awesome Oscillator (AO), respectivamente. Inicializamos ambas as variáveis com o valor INVALID_HANDLE, uma constante predefinida no MQL5 que representa um identificador inválido ou não inicializado. Isso é importante porque quando criamos um indicador, o sistema retorna um identificador que nos permite interagir com ele. Se o identificador for “INVALID_HANDLE”, significa que a criação do indicador falhou ou não foi inicializada corretamente. Ao definir os identificadores como INVALID_HANDLE inicialmente, garantimos que posteriormente possamos verificar falhas de inicialização e lidar adequadamente com erros.
Em seguida, precisamos inicializar arrays onde armazenamos os valores recuperados.
double senkouSpan_A[]; //--- Array to store Senkou Span A values double senkouSpan_B[]; //--- Array to store Senkou Span B values double awesome_Oscillator[]; //--- Array to store Awesome Oscillator values
Ainda no escopo global, declaramos três arrays: “senkouSpan_A”, “senkouSpan_B” e “awesome_Oscillator”, que usamos para armazenar os valores do Senkou Span A, Senkou Span B e do Awesome Oscillator, respectivamente. Definimos esses arrays como tipo double, o que significa que eles armazenarão valores de ponto flutuante, adequados para armazenar resultados de cálculos de indicadores. Usamos os arrays “senkouSpan_A” e “senkouSpan_B” para armazenar os valores dos componentes Senkou Span A e B do indicador Ichimoku. Em contraste, o array “awesome_Oscillator” armazena os valores calculados pelo Awesome Oscillator. Ao declarar esses arrays, preparamos o armazenamento dos valores dos indicadores para posterior acesso e uso na lógica de trading.
Essas são todas as variáveis de que precisamos no escopo global. Agora podemos inicializar os identificadores dos indicadores no manipulador de evento OnInit, que é uma função responsável pelo ciclo de inicialização.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- return(INIT_SUCCEEDED); //--- Return successful initialization }
Este é um manipulador de evento chamado sempre que o indicador é inicializado por qualquer motivo. Dentro dele, inicializamos os identificadores dos indicadores. Começamos com o identificador do Kumo.
//--- Initialize the Ichimoku Kumo indicator handle_Kumo = iIchimoku(_Symbol,_Period,8,29,34); if (handle_Kumo == INVALID_HANDLE){ //--- Check if Kumo indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE KUMO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure }
Aqui, inicializamos “handle_Kumo” chamando a função iIchimoku, que cria uma instância do indicador Ichimoku Kumo para o símbolo atual (_Symbol) e período (_Period). Usamos os parâmetros específicos do indicador Ichimoku: 8, 29 e 34 períodos para Tenkan-sen, Kijun-sen e Senkou Span B, respectivamente, conforme ilustrado anteriormente.
Após chamar iIchimoku, a função retorna um identificador, que armazenamos em “handle_Kumo”. Em seguida, verificamos se “handle_Kumo” é igual a INVALID_HANDLE, o que indicaria que a inicialização do indicador falhou. Se o identificador for inválido, registramos uma mensagem de erro com a função “Print”, que especifica o motivo da falha, e retornamos a constante INIT_FAILED, indicando que o processo de inicialização não foi bem-sucedido. Da mesma forma, inicializamos o indicador oscilador.
//--- Initialize the Awesome Oscillator handle_AO = iAO(_Symbol,_Period); if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure }
Para inicializar o oscilador, chamamos a função iAO e passamos apenas o símbolo e o período como parâmetros padrão. Continuamos então com o restante da lógica de inicialização no mesmo formato usado para o identificador do Kumo. Após a inicialização, podemos prosseguir configurando os arrays de armazenamento como séries temporais.
ArraySetAsSeries(senkouSpan_A,true); //--- Set Senkou Span A array as a time series ArraySetAsSeries(senkouSpan_B,true); //--- Set Senkou Span B array as a time series ArraySetAsSeries(awesome_Oscillator,true); //--- Set Awesome Oscillator array as a time series
Usamos a função ArraySetAsSeries para definir os arrays “senkouSpan_A”, “senkouSpan_B” e “awesome_Oscillator” como arrays de séries temporais. Ao definir esses arrays como séries temporais, garantimos que os valores mais recentes sejam armazenados no início do array, enquanto os valores mais antigos vão para o final. Isso é importante porque, no MQL5, dados de séries temporais são normalmente organizados de forma que os valores mais recentes sejam acessados primeiro (no índice 0), facilitando a recuperação dos dados mais recentes para decisões de trading.
Chamamos ArraySetAsSeries em cada array, passando true como segundo argumento para habilitar esse comportamento de série temporal. Isso nos permite trabalhar com os dados de uma maneira que se alinha com estratégias de trading típicas, onde frequentemente precisamos acessar primeiro os valores mais recentes. Por fim, quando toda a inicialização é concluída, podemos imprimir uma mensagem no diário para indicar que o sistema está pronto.
Print("SUCCESS. ",__FILE__," HAS BEEN INITIALIZED."); //--- Log successful initialization
Após uma inicialização bem-sucedida, usamos a função Print para registrar uma mensagem indicando que o processo de inicialização foi bem-sucedido. A mensagem inclui a string “SUCCESS.”, seguida da variável predefinida FILE, que representa o nome do arquivo de código-fonte atual. Ao usar FILE, podemos inserir dinamicamente o nome do arquivo na mensagem de log, o que pode ajudar na depuração ou rastreamento do processo de inicialização em projetos maiores com múltiplos arquivos. A mensagem será exibida no terminal ou no arquivo de log, confirmando que a inicialização foi concluída com sucesso. Essa etapa ajuda a garantir que tenhamos feedback adequado sobre o status da inicialização, facilitando a identificação de possíveis problemas no código.
O trecho completo do código de inicialização é o seguinte:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Initialize the Ichimoku Kumo indicator handle_Kumo = iIchimoku(_Symbol,_Period,8,29,34); if (handle_Kumo == INVALID_HANDLE){ //--- Check if Kumo indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE KUMO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure } //--- Initialize the Awesome Oscillator handle_AO = iAO(_Symbol,_Period); if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure } ArraySetAsSeries(senkouSpan_A,true); //--- Set Senkou Span A array as a time series ArraySetAsSeries(senkouSpan_B,true); //--- Set Senkou Span B array as a time series ArraySetAsSeries(awesome_Oscillator,true); //--- Set Awesome Oscillator array as a time series Print("SUCCESS. ",__FILE__," HAS BEEN INITIALIZED."); //--- Log successful initialization //--- return(INIT_SUCCEEDED); //--- Return successful initialization }
Isso fornece a seguinte saída.

Como inicializamos arrays de armazenamento de dados e identificadores, não queremos mantê-los após desinicializar o programa, pois ocupariam recursos desnecessários. Lidamos com isso no manipulador de evento OnDeinit, chamado sempre que o programa é desinicializado, seja qual for o motivo.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Free memory allocated for Senkou Span A and B arrays ArrayFree(senkouSpan_A); ArrayFree(senkouSpan_B); //--- Free memory allocated for the Awesome Oscillator array ArrayFree(awesome_Oscillator); }
Dentro da função OnDeinit, executamos tarefas de limpeza para liberar qualquer memória alocada durante o processo de inicialização. Especificamente, usamos a função ArrayFree para desalocar a memória dos arrays “senkouSpan_A”, “senkouSpan_B” e “awesome_Oscillator”. Esses arrays foram usados anteriormente para armazenar os valores do indicador Ichimoku Kumo e do Awesome Oscillator, e agora que não são mais necessários, liberamos a memória para evitar vazamentos de recursos. Ao fazer isso, garantimos que o programa gerencie os recursos do sistema de forma eficiente e evite o uso desnecessário de memória após o Expert Advisor não estar mais ativo.
Tudo o que resta agora é tratar a lógica de negociação, onde recuperamos os valores dos indicadores e os analisamos para tomar decisões de trading. Tratamos isso no manipulador de eventos OnTick, que é chamado sempre que há um novo tick ou simplesmente alterações de preço. O primeiro passo que precisamos fazer é recuperar pontos de dados dos identificadores de indicador e armazená-los para análise posterior.
//--- Copy data for Senkou Span A from the Kumo indicator if (CopyBuffer(handle_Kumo,SENKOUSPANA_LINE,0,2,senkouSpan_A) < 2){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM SENKOUSPAN A LINE. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails } //--- Copy data for Senkou Span B from the Kumo indicator if (CopyBuffer(handle_Kumo,SENKOUSPANB_LINE,0,2,senkouSpan_B) < 2){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM SENKOUSPAN B LINE. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails }
Aqui, usamos a função CopyBuffer para copiar dados das linhas Senkou Span A e Senkou Span B do indicador Kumo (Ichimoku) para os arrays "senkouSpan_A" e "senkouSpan_B", respectivamente. O primeiro argumento passado a CopyBuffer é o identificador do indicador "handle_Kumo", que se refere ao indicador Kumo inicializado. O segundo argumento especifica qual linha de dados copiar: "SENKOUSPANA_LINE" para Senkou Span A e "SENKOUSPANB_LINE" para Senkou Span B. O terceiro argumento é o índice inicial a partir do qual começar a copiar, definido como 0 para iniciar pelos dados mais recentes. O quarto argumento especifica o número de pontos de dados a copiar, que é 2 neste caso. O último argumento é o array onde os dados serão armazenados, "senkouSpan_A" ou "senkouSpan_B".
Após chamar CopyBuffer, verificamos se a função retorna um valor menor que 2, o que indica que os dados solicitados não foram copiados com sucesso. Se isso ocorrer, registramos uma mensagem de erro com a função Print, especificando que os dados não puderam ser copiados da respectiva linha Senkou Span, e então saímos da função usando return. Isto garante que, se a cópia dos dados falhar, tratemos o erro de forma elegante registrando o problema e interrompendo a execução adicional da função.
Usamos a mesma lógica para recuperar os valores do oscilador.
//--- Copy data from the Awesome Oscillator if (CopyBuffer(handle_AO,0,0,3,awesome_Oscillator) < 3){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM AWESOME OSCILLATOR. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails }
Utilizamos a função CopyBuffer para copiar dados do indicador Awesome Oscillator (AO) para o array "awesome_Oscillator". O primeiro argumento passado à função CopyBuffer é o identificador do indicador "handle_AO", que se refere ao Awesome Oscillator inicializado. O segundo argumento especifica a linha de dados ou índice do buffer a copiar, que é 0 neste caso, já que o Awesome Oscillator possui um único buffer de dados. O terceiro argumento é o índice de início, definido como 0 para começar a copiar pelos dados mais recentes. O quarto argumento especifica o número de pontos de dados a copiar, que é definido como 3 neste caso, ou seja, queremos copiar os três valores mais recentes. O último argumento é o array "awesome_Oscillator" onde os dados copiados serão armazenados. Se os dados recuperados forem menores que os solicitados, registramos uma mensagem de erro e retornamos.
Se tivermos todos os dados necessários, podemos continuar a processá-los. A primeira coisa que precisamos fazer é definir uma lógica que assegure que analisamos os dados uma vez quando houver uma nova barra completa gerada e não a cada tick. Incorporamos essa lógica em uma função.
//+------------------------------------------------------------------+ //| IS NEW BAR FUNCTION | //+------------------------------------------------------------------+ bool isNewBar(){ static int prevBars = 0; //--- Store previous bar count int currBars = iBars(_Symbol,_Period); //--- Get current bar count for the symbol and period if (prevBars == currBars) return (false); //--- If bars haven't changed, return false prevBars = currBars; //--- Update previous bar count return (true); //--- Return true if new bar is detected }
Definimos uma função boolean "isNewBar", que é usada para detectar se uma nova barra apareceu no gráfico para o símbolo e período especificados. Dentro desta função, declaramos uma variável estática "prevBars", que armazena a contagem de barras do verificação anterior. A palavra-chave static assegura que a variável retenha seu valor entre chamadas da função.
Em seguida, usamos a função iBars para obter o número atual de barras no gráfico para o símbolo dado (_Symbol) e período (_Period). O resultado é armazenado na variável "currBars". Se o número de barras não mudou (ou seja, "prevBars" é igual a "currBars"), retornamos false, indicando que nenhuma nova barra apareceu. Se o número de barras mudou, atualizamos "prevBars" com a contagem atual de barras e retornamos true, sinalizando que uma nova barra foi detectada. Munidos dessa função, podemos chamá-la dentro do manipulador de tick e proceder à análise.
//--- Check if a new bar has formed if (isNewBar()){ //--- Determine if the AO has crossed above or below zero bool isAO_Above = awesome_Oscillator[1] > 0 && awesome_Oscillator[2] < 0; bool isAO_Below = awesome_Oscillator[1] < 0 && awesome_Oscillator[2] > 0; //--- }
Aqui, verificamos se uma nova barra se formou chamando a função "isNewBar". Se uma nova barra for detectada (ou seja, "isNewBar" retornar true), procedemos para determinar o comportamento do Awesome Oscillator (AO).
Definimos duas variáveis booleanas: "isAO_Above" e "isAO_Below". A variável "isAO_Above" é definida como true se o valor anterior do Awesome Oscillator (awesome_Oscillator[1]) for maior que zero, e o valor anterior a esse (awesome_Oscillator[2]) for menor que zero. Essa condição verifica se o AO cruzou acima de zero, indicando um potencial sinal de alta. De forma semelhante, "isAO_Below" é definida como true se o valor anterior do AO (awesome_Oscillator[1]) for menor que zero e o valor anterior a esse (awesome_Oscillator[2]) for maior que zero, indicando que o AO cruzou abaixo de zero, o que pode sinalizar um movimento de baixa. Podemos então usar o mesmo método para definir as demais lógicas.
//--- Determine if the Kumo is bullish or bearish bool isKumo_Above = senkouSpan_A[1] > senkouSpan_B[1]; bool isKumo_Below = senkouSpan_A[1] < senkouSpan_B[1]; //--- Determine buy and sell signals based on conditions bool isBuy_Signal = isAO_Above && isKumo_Below && getClosePrice(1) > senkouSpan_A[1] && getClosePrice(1) > senkouSpan_B[1]; bool isSell_Signal = isAO_Below && isKumo_Above && getClosePrice(1) < senkouSpan_A[1] && getClosePrice(1) < senkouSpan_B[1];
Aqui, determinamos as condições para uma configuração Kumo (Ichimoku) de alta ou baixa. Primeiro, definimos duas variáveis booleanas: "isKumo_Above" e "isKumo_Below". A variável "isKumo_Above" é definida como true se o valor anterior de Senkou Span A (senkouSpan_A[1]) for maior que o valor anterior de Senkou Span B (senkouSpan_B[1]), indicando um Kumo altista (sentimento de mercado de alta). Por outro lado, "isKumo_Below" é definida como true se Senkou Span A for menor que Senkou Span B, indicando um Kumo baixista (sentimento de mercado de baixa).
Em seguida, definimos as condições para potenciais sinais de compra e venda. O sinal de compra ("isBuy_Signal") é definido como true se as seguintes condições forem atendidas: o Awesome Oscillator cruzou acima de zero (isAO_Above), o Kumo está baixista (isKumo_Below) e o preço de fechamento da barra anterior está acima tanto do Senkou Span A quanto do Senkou Span B. Isso sugere um potencial movimento de alta apesar do Kumo estar baixista. O sinal de venda ("isSell_Signal") é definido como true se o Awesome Oscillator cruzou abaixo de zero (isAO_Below), o Kumo está altista (isKumo_Above) e o preço de fechamento da barra anterior está abaixo tanto do Senkou Span A quanto do Senkou Span B. Isso indica um potencial movimento de baixa apesar do Kumo estar altista.
Você pode ter notado que usamos uma nova função para obter os preços de fechamento. Aqui está a lógica de todas as funções que precisaremos.
//+------------------------------------------------------------------+ //| FUNCTION TO GET CLOSE PRICES | //+------------------------------------------------------------------+ double getClosePrice(int bar_index){ return (iClose(_Symbol, _Period, bar_index)); //--- Retrieve the close price of the specified bar } //+------------------------------------------------------------------+ //| FUNCTION TO GET ASK PRICES | //+------------------------------------------------------------------+ double getAsk(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); //--- Get and normalize the Ask price } //+------------------------------------------------------------------+ //| FUNCTION TO GET BID PRICES | //+------------------------------------------------------------------+ double getBid(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); //--- Get and normalize the Bid price }
Aqui, definimos três funções para recuperar diferentes tipos de dados de preço:
- Função "getClosePrice": esta função recupera o preço de fechamento de uma barra especificada. Ela recebe um parâmetro "bar_index", que representa o índice da barra para a qual queremos obter o preço de fechamento. A função chama a função interna iClose, passando o símbolo (_Symbol), o período (_Period) e o índice da barra para obter o preço de fechamento da barra especificada. O preço recuperado é retornado como double.
- Função "getAsk": esta função recupera o preço Ask atual para o símbolo dado. Ela usa a função SymbolInfoDouble com a constante SYMBOL_ASK para obter o preço Ask. O resultado é então normalizado usando a função NormalizeDouble para garantir que o preço seja arredondado ao número correto de casas decimais com base na propriedade _Digits do símbolo. Esta função retorna o preço Ask normalizado como double.
- Função "getBid": esta função recupera o preço Bid atual para o símbolo dado. Semelhante à função "getAsk", ela usa SymbolInfoDouble com a constante SYMBOL_BID para obter o preço Bid, e então normaliza usando a função NormalizeDouble para garantir que corresponda à precisão correta definida pela propriedade _Digits do símbolo. Esta função retorna o preço Bid normalizado como double.
Essas funções fornecem uma maneira fácil de recuperar e normalizar os preços relevantes para decisões de trading dentro do programa. Podemos então usar os sinais de trade calculados e abrir as posições respectivas para os sinais existentes.
if (isBuy_Signal){ //--- If buy signal is generated Print("BUY SIGNAL GENERATED @ ",iTime(_Symbol,_Period,1),", PRICE: ",getAsk()); //--- Log buy signal obj_Trade.Buy(0.01,_Symbol,getAsk()); //--- Execute a buy trade } else if (isSell_Signal){ //--- If sell signal is generated Print("SELL SIGNAL GENERATED @ ",iTime(_Symbol,_Period,1),", PRICE: ",getBid()); //--- Log sell signal obj_Trade.Sell(0.01,_Symbol,getBid()); //--- Execute a sell trade }
Verificamos se um sinal de compra ou venda foi gerado e executamos o trade correspondente. Se "isBuy_Signal" for true, indicando que ocorreu um sinal de compra, primeiro registramos o evento usando a função Print. Incluímos o carimbo de data/hora da barra anterior, que é recuperado com a função iTime, e o preço Ask atual, obtido pela função "getAsk". Esse registro fornece um histórico do sinal de compra e do preço no qual ele ocorreu. Após registrar, executamos o trade de compra chamando "obj_Trade.Buy(0.01, _Symbol, getAsk())", que coloca uma ordem de compra de 0.01 lotes ao preço Ask atual.
De forma semelhante, se "isSell_Signal" for true, indicando um sinal de venda, registramos o evento com a função Print, que inclui o carimbo de data/hora da barra anterior e o preço Bid atual obtido pela função "getBid". Após registrar, colocamos um trade de venda usando "obj_Trade.Sell(0.01, _Symbol, getBid())", que executa uma ordem de venda de 0.01 lotes ao preço Bid atual. Isso garante que trades sejam realizados sempre que as condições para sinais de compra ou venda forem atendidas, mantendo um registro claro dessas ações.
Finalmente, só precisamos verificar mudanças de momentum e fechar as posições respectivas. Segue a lógica:
if (isAO_Above || isAO_Below){ //--- If AO crossover occurs if (PositionsTotal() > 0){ //--- If there are open positions for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through open positions ulong posTicket = PositionGetTicket(i); //--- Get the position ticket if (posTicket > 0){ //--- If ticket is valid if (PositionSelectByTicket(posTicket)){ //--- Select position by ticket ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type if (posType == POSITION_TYPE_BUY){ //--- If position is a buy if (isAO_Below){ //--- If AO indicates bearish crossover Print("CLOSING THE BUY POSITION WITH #",posTicket); //--- Log position closure obj_Trade.PositionClose(posTicket); //--- Close the buy position } } else if (posType == POSITION_TYPE_SELL){ //--- If position is a sell if (isAO_Above){ //--- If AO indicates bullish crossover Print("CLOSING THE SELL POSITION WITH #",posTicket); //--- Log position closure obj_Trade.PositionClose(posTicket); //--- Close the sell position } } } } } } }
Aqui, verificamos um crossover do Awesome Oscillator (AO) (acima ou abaixo de zero) e gerenciamos as posições abertas em conformidade. Se "isAO_Above" ou "isAO_Below" for true, indicando que ocorreu um crossover do AO, prosseguimos para verificar se existem posições abertas chamando a função PositionsTotal. Se houver posições abertas (ou seja, "PositionsTotal" retornar um valor maior que 0), fazemos um loop por todas as posições abertas, começando pela mais recente (PositionsTotal()-1) e retrocedendo.
Dentro do loop, recuperamos o ticket da posição usando a função PositionGetTicket. Se o ticket da posição for válido (ou seja, maior que 0), selecionamos a posição usando a função PositionSelectByTicket. Em seguida, determinamos o tipo de posição chamando PositionGetInteger. Se a posição for uma compra (POSITION_TYPE_BUY), verificamos se "isAO_Below" é true, indicando um crossover baixista. Se for true, registramos o encerramento da posição de compra usando a função Print e fechamos a posição com "obj_Trade.PositionClose(posTicket)".
De forma similar, se a posição for uma venda (POSITION_TYPE_SELL), verificamos se "isAO_Above" é true, indicando um crossover altista. Se for true, registramos o encerramento da posição de venda e a fechamos usando "obj_Trade.PositionClose(posTicket)". Isto garante que gerenciemos as posições abertas de forma eficaz, fechando-as quando as condições de um crossover do AO sinalizarem uma mudança no momentum do mercado. Ao rodar o programa, temos a seguinte saída:
Confirmação da posição de venda.

Confirmação de saída de posição de venda por mudança de momentum de mercado:

A partir das ilustrações acima, podemos ter certeza de que alcançamos os objetivos desejados. Agora podemos prosseguir para testar e otimizar o programa. Isso será tratado na próxima seção.
Testes e Otimização da Estratégia
Nesta seção, testamos a estratégia e a otimizamos para funcionar melhor em várias condições de mercado. A alteração que faremos é no setor de gerenciamento de risco, onde podemos adicionar um trailing stop para travar lucros quando já estivermos no lucro, em vez de esperar que o mercado tome uma decisão completa sobre a mudança de momentum. Para lidar com isso de forma eficiente, construiremos uma função dinâmica para tratar a lógica do trailing stop.
//+------------------------------------------------------------------+ //| FUNCTION TO APPLY TRAILING STOP | //+------------------------------------------------------------------+ void applyTrailingSTOP(double slPoints, CTrade &trade_object,int magicNo=0){ double buySL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID)-slPoints,_Digits); //--- Calculate SL for buy positions double sellSL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK)+slPoints,_Digits); //--- Calculate SL for sell positions for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Iterate through all open positions ulong ticket = PositionGetTicket(i); //--- Get position ticket if (ticket > 0){ //--- If ticket is valid if (PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)){ //--- Check symbol and magic number if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buySL > PositionGetDouble(POSITION_PRICE_OPEN) && (buySL > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)){ //--- Modify SL for buy position if conditions are met trade_object.PositionModify(ticket,buySL,PositionGetDouble(POSITION_TP)); } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellSL < PositionGetDouble(POSITION_PRICE_OPEN) && (sellSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)){ //--- Modify SL for sell position if conditions are met trade_object.PositionModify(ticket,sellSL,PositionGetDouble(POSITION_TP)); } } } } }
Aqui, implementamos uma função para aplicar um trailing stop às posições abertas. A função é chamada "applyTrailingSTOP" e recebe três parâmetros: "slPoints", que representa o número de pontos para definir o stop-loss; "trade_object", que é uma referência ao objeto de trade usado para modificar posições; e um opcional "magicNo", que é usado para identificar posições específicas pelo seu número mágico. Primeiro, calculamos os níveis de stop-loss (SL) para posições de compra e venda. Para posições de compra, o stop-loss é definido no preço Bid menos os "slPoints" especificados, e para posições de venda, o stop-loss é definido no preço Ask mais os "slPoints" especificados. Ambos os valores de SL são normalizados usando a função NormalizeDouble para corresponder à precisão decimal do símbolo, definida pela variável _Digits.
Em seguida, iteramos por todas as posições abertas usando a função PositionsTotal, percorrendo da posição mais recente até a mais antiga. Para cada posição, recuperamos o ticket da posição usando a função PositionGetTicket e garantimos que seja válido. Então verificamos se o símbolo da posição corresponde ao símbolo atual (_Symbol) e se o número mágico da posição coincide com o "magicNo" fornecido, a menos que o número mágico esteja definido como 0, caso em que todas as posições são consideradas.
Se a posição for de compra (POSITION_TYPE_BUY), verificamos se o stop-loss de compra calculado ("buySL") está acima do preço de abertura da posição (POSITION_PRICE_OPEN) e se é maior que o stop-loss atual (POSITION_SL) ou se o stop-loss atual não está definido ("POSITION_SL" == 0). Se essas condições forem atendidas, atualizamos o stop-loss da posição chamando "trade_object.PositionModify(ticket, buySL, PositionGetDouble(POSITION_TP))", que modifica o stop-loss da posição mantendo o take-profit ("POSITION_TP") inalterado.
Se a posição for de venda (POSITION_TYPE_SELL), aplicamos uma lógica similar. Verificamos se o stop-loss de venda calculado ("sellSL") está abaixo do preço de abertura da posição (POSITION_PRICE_OPEN) e se é menor que o stop-loss atual (POSITION_SL) ou se o stop-loss atual não está definido. Se essas condições forem atendidas, atualizamos o stop-loss da posição usando "trade_object.PositionModify(ticket, sellSL, PositionGetDouble(POSITION_TP))".
Após definir a função, só precisamos chamá-la na função de tick para que a execução ocorra. Fazemos isso chamando-a e passando os respectivos parâmetros conforme segue.
if (PositionsTotal() > 0){ //--- If there are open positions applyTrailingSTOP(3000*_Point,obj_Trade,0); //--- Apply a trailing stop }
Fazemos isso chamando-a e passando os respectivos parâmetros conforme segue. A função é chamada com três argumentos:
- Pontos do Trailing Stop: A distância do stop-loss é calculada como "3000 * _Point", onde _Point representa o menor movimento de preço possível para o símbolo atual. Isso significa que o stop-loss é definido a 3000 pontos de distância do preço de mercado atual.
- Objeto de Trade: Passamos "obj_Trade", que é uma instância do objeto de trade usado para modificar os níveis de stop-loss e take-profit da posição.
- Número Mágico: O terceiro argumento é definido como 0, significando que a função aplicará o trailing stop a todas as posições abertas, independentemente do seu número mágico.
Após a aplicação do trailing stop, obtemos a seguinte saída.

A partir da visualização, podemos ver que, em vez de esperar pela mudança no momentum de mercado, travamos nossos lucros e maximizamos os ganhos movendo o nível do stop loss toda vez que o mercado avança em nossa direção. Os resultados finais do testador de estratégia são os seguintes.
Gráfico do Testador:


Conclusão
Em conclusão, este artigo demonstrou como construir um Expert Advisor (EA) em MQL5 usando o sistema Kumo Breakout. Ao integrar o Ichimoku Kumo e o Awesome Oscillator (AO), criamos uma estrutura para detectar mudanças de momentum de mercado e sinais de breakout. As etapas-chave incluíram configurar identificadores de indicadores, extrair valores-chave e automatizar a execução de trades com trailing stops e gerenciamento de posições, resultando em um EA orientado por estratégia com lógica de negociação robusta.
Aviso: Este artigo é um guia educacional para desenvolvimento de EAs em MQL5 baseados em sinais de trade gerados por indicadores. Embora o sistema Kumo Breakout seja uma estratégia popular, sua eficácia não é garantida em todas as condições de mercado. Negociar envolve risco financeiro, e desempenho passado não garante resultados futuros. Testes completos e gerenciamento de risco adequado são essenciais antes de operar em conta real.
Seguindo este guia, você pode aprimorar suas habilidades de desenvolvimento em MQL5 e criar sistemas de trading mais sofisticados. Os conceitos de integração de indicadores, lógica de sinais e automação de trades demonstrados aqui podem ser aplicados a outras estratégias, incentivando maior exploração e inovação em trading algorítmico. Feliz codificação e bons trades!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16657
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Implementação do modelo de tabela em MQL5: Aplicação do conto MVC
Integre seu próprio LLM ao EA (Parte 5): Desenvolva e Teste Estratégia de Trading com LLMs (III) – Adapter-Tuning
Como construir e otimizar um sistema de trading baseado em volume (Chaikin Money Flow - CMF)
Dominando Operações de Arquivos em MQL5: Do I/O Básico à Construção de um Leitor CSV Personalizado
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Muito bom, cara!
Muito obrigado. Agradeço muito seu feedback gentil.