Criando um robô trader do zero com mql5 — Parte 1

Kaio Valente
devtrader
Published in
7 min readApr 22, 2020

Neste artigo vou abordar o construção de um robô (Expert Advisor — EA) para a plataforma metatrader5, desenvolvido na linguagem mql5. Este robô foi desenvolvido para operar mini contratos futuros de índice na bolsa brasileira B3. Mas pode ser facilmente adaptado para executar em outros ativos. O código completo está disponível aqui.

A estratégia implementada no robô é o famoso setup 9.1 desenvolvido (supostamente) pelo Larry Williams e difundido no Brasil pelo trader Palex, os detalhes da estratégia podem ser encontrados aqui. Por se tratar de uma estratégia bem simples, é um exemplo ideal para mostrar a estrutura e as operações básicas da linguagem.

Créditos da imagem: http://atpalex.blogspot.com/2014/08/setup-91.html

O que você vai aprender a fazer de forma automática neste artigo:

  • Como manipular um indicador de média móvel
  • Enviar ordens pendentes de compra e venda, setando stop loss e stop gain (take profit)
  • Fechar ordens pendentes e posições abertas
  • Alterar stop loss de operações abertas

Estrutura inicial de um robô mql5

Basicamente há 3 seções iniciais: #property, #include e input

#property: São metadados do seu robô. Existem várias propriedades diferentes, desde informações básicas como versão e descrição do seu EA, até a configuração dos indicadores na área do gráfico.

#include: Usado para especificar a importação de bibliotecas externas. Neste exemplo vamos importar a biblioteca SymbolInfo.mqh

input: São propriedades que poderão ser atribuídas pelo usuário na inicialização do robô. Permite parametrizar o comportamento da estratégia de acordo com sua necessidade.

Temos 7 atributos que poderão ser parametrizados pelo usuário:

  • TimeFrame: TimeFrame em que o robô irá executar
  • Media: Período da media móvel, por padrão, vamos usar a média de 9 períodos
  • TP: Número de pontos de take profit
  • Volume: Quantidade de contratos por operação
  • HoraInicial: Hora inicial para o robô começar a executar
  • HoraFinal: Hora de término para novas operações, após esse horário o robô não envia novas ordens
  • HoraFechamento: Se ainda houver posições abertas serão encerradas neste horário.

Ao adicionar o robô no ativo que se deseja operar, a tela de inicialização será semelhante a essa:

Configuração de paramêtros

Variáveis Globais

Nesta seção são declaradas variáveis que vamos utilizar durante toda execução do robô, estão todas com comentários autoexplicativos e vamos ver com mais detalhes no decorrer deste artigo. Vale salientar a importância da variável magic_number, esta variável é utilizada como identificador único de cada EA, caso você queira rodar vários robôs no mesmo ativo, cada um deve ter um magic number diferente, você também pode criar este identificador como um input do usuário.

Eventos

Existem funções que são executadas sempre que um evento ocorrer, são chamadas de Event Handlers. A estrutura básica de um EA é baseada em eventos, sempre que ocorrer uma atualização no preço (novo tick) de um ativo a função OnTick() será chamada, sempre que houver a inicialização de um robô a função OnInit() será chamada.

Normalmente, na função OnInit() é onde é feita a validação inicial dos inputs feitos pelo usuário e o carregamento dos indicadores necessários para execução da estratégia. Caso tenha algum problema na inicialização, logamos as mensagens de erro.

A função iMA é responsável por carregar o indicador de média móvel e recebe como parâmetros o ativo, time frame e o período em que deve ser carregada, além do tipo de média, no nosso caso será uma média exponencial (MODE_EMA) e o preço em que é aplicada, usaremos o padrão PRICE_CLOSE.

Na função OnTick() é onde toda mágica acontece, a cada novo preço do ativo, essa função é invocada. Logo, toda lógica para abrir e fechar novas posições devem ser processadas aqui.

A idéia principal aqui é sempre que houver um novo candle, devemos verificar se a média virou pra cima, nesse caso lançaremos uma ordem de compra na superação da máxima do candle. Ou se a média virar para baixo, vamos enviar uma ordem de venda na superação da mínima deste candle. A função responsável por verificar se há um sinal de compra ou venda é a CheckSinal().

Ainda na função OnTick(), se houver alguma posição já aberta devemos verificar se algum sinal contrário é lançado, neste caso devemos alterar o stop loss desta posição. Exemplo: Se estivermos vendidos e a média virar para cima, devemos atualizar nosso stop loss para a máxima deste candle. A função responsável por esta checagem é CheckPosicaoAberta().

Da mesma forma que verificamos posições abertas, também devemos verificar ordens abertas (atente para a diferença entre Ordem e Posição). Caso seja lançada uma ordem de venda, mas esta ainda não foi executada e em seguida um novo sinal de compra é ativado, a ordem de venda anterior deve ser removida imediatamente. Este é o papel da função CheckOrdemAberta().

Finalmente podemos verificar se devemos abrir uma nova posição caso um sinal de compra ou venda seja ativado e o horário para novas operações esteja válido. A função CheckNovaEntrada() faz esse trabalho. Note que sempre que lançamos uma nova ordem de entrada, armazenamos o último sinal na variável ultimo_sinal, isso é necessário para controlar qual deve ser a direção da próxima operação. Se estivermos vendidos, o próximo trade deve ser na compra.

Manipulando média móvel e verificando sinal de entrada

Como foi dito, a função CheckSinal() verifica se a média virou pra cima ou pra baixo para indicar um sinal de compra ou venda. A função CopyBuffer é responsável por obter os dados da média móvel e atribuí-los para um array, neste caso, obtemos apenas os 2 últimos valores, que é o suficiente para a nossa checagem, em seguida o array é ordenado de forma que os primeiros valores do array são os últimos valores da média móvel. Logo media_buffer[0] retorna o último valor da média, media_buffer[1] o penúltimo valor, e assim por diante…

Roteamento: Como enviar ordens para corretora

Sem dúvida esse é o ponto mais importante quando estamos desenvolvendo um robô pois envolve integrações externas ao nosso sistema, quando se abre uma requisição é enviada uma solicitação a corretora e em seguida é obtida uma resposta com sucesso ou erro, este processo deve ser feito com segurança para evitar problemas no envio de ordens. Neste exemplo vamos implementar todas as transações de roteamento utilizando as classes MqlTradeRequest, MqlTradeResult e MqlTradeCheckResult, com o objetivo de apresentar de forma didática como utilizar estas classes. Porém todas as operações de envio de ordens podem ser feitas diretamente com a utilização da classe CTrade.

A função Comprar() é responsável por enviar uma ordem pendente de compra acima da máxima do candle de sinal.

Primeiramente é obtido o preço de entrada da ordem, que deve ser a máxima do candle de sinal. Em seguida obtemos os preços de stop loss, que será a mínima do candle de sinal e o take profit que é preço de entrada mais o valor em pontos definido como input. A função ZerarRequest() limpa os objetos de requisição de novas ordens. Finalmente montamos o objeto request que contém todas as informações referentes a nossa ordem, são elas:

request.action: Define a ação que será executada. Neste caso, uma ordem pendente (TRADE_ACTION_PENDING)

request.magic: Identificador do nosso EA

request.symbol: Ativo que se deseja operar

request.volume: Quantidade de contratos da operação

request.price: Preço de entrada em que a ordem será lançada

request.sl: Preço de Stop Loss

request.tp: Preço de Take Profit

request.type: Tipo de ordem. Neste caso, uma ordem stop de compra (ORDER_TYPE_BUY_STOP)

request.type_filling: Define o tipo de execução da ordem

request.type_time: Define o tipo de expiração da ordem

request.comment: Atribui um comentário a requisição

A função EnviarRequisicao() é quem faz o envio das ordens de fato

Antes de enviar uma nova requisição de roteamento, devemos sempre limpar os objetos de requisição, a função ResetLastError() faz esse serviço. A função OrderCheck() é responsável por verificar se objeto de requisição está definido com valores válidos, caso não esteja logamos a mensagem de erro. Por fim, a requisição é enviada com a função OrderSend(), da mesma forma, logamos se houver algum erro. A função de venda é semelhante a de compra, porém com request.type=ORDER_TYPE_SELL_STOP.

As demais funções que envolvem roteamento são FecharOrdens(), FecharPosicao() e AlterarStopLoss(), a lógica de roteamento segue a mesma, o que muda é o atributo request.action, são eles:

TRADE_ACTION_REMOVE: Usado para remover ordens abertas.

TRADE_ACTION_DEAL: Usado para enviar ordens a mercado. A função FecharPosicao() usa para enviar ordens a mercado no sentido oposto da operação aberta e assim fechá-la.

TRADE_ACTION_SLTP: Usado para alterar uma ordem em aberto. Na função AlterarStopLoss() usamos para alterar o preço de stop loss da operação aberta.

Pronto, cobrimos todos os pontos necessários para desenvolver um robô do zero: definir inputs de usuário, declaração de variáveis globais, lidar com eventos de novos preços do ativo, manipular indicadores e roteamento de ordens.

O código completo deste robô se encontra no meu github.

No próximo artigo vou evoluir este robô incluindo novas funcionalidades: executar realização parcial, ativar break even, adicionar limite de gain e limite de loss diário.

Você pode rodar backtests no passado para verificar os resultado e testar diferentes parâmetros de médias, timeframes e take profit.

É importante falar que este robô foi desenvolvido para fins didáticos e que rodá-lo em conta demo ou real são de sua responsabilidade.

Se você gostou desse tipo de conteúdo e gostaria de acompanhar, siga @devtrader no instagram.

--

--