O EA de Média Móvel está incluído no pacote padrão do MetaTrader 5 e serve como um exemplo prático de como operar utilizando o indicador de Média Móvel.
O arquivo do EA, chamado Moving Average.mq5, pode ser encontrado na pasta "terminal_data_folder\MQL5\Experts\Examples\Moving Average". Este EA é um exemplo de uso de indicadores técnicos, funções de histórico de operações e classes de negociação da Biblioteca Padrão. Além disso, o EA inclui um sistema de gestão de risco baseado nos resultados das operações.
Vamos explorar a estrutura do Expert Advisor e como ele funciona.
1. Propriedades do EA
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2009-2013, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00"
As cinco primeiras linhas contêm um comentário e as três seguintes definem as propriedades do programa MQL5 (direitos autorais, link e versão) utilizando as diretivas do pré-processador #property.
Quando você executar o Expert Advisor, essas informações aparecerão na aba "Comum":

Figura 1. Parâmetros Comuns do EA de Média Móvel
1.2. Arquivos Inclusos
A diretiva #include informa ao compilador para incluir o arquivo "Trade.mqh".
Esse arquivo é parte da Biblioteca Padrão e contém a classe CTrade para facilitar o acesso às funções de negociação.
#include <Trade\Trade.mqh>
O nome do arquivo incluído aparece entre colchetes "<>;", então o caminho é definido em relação ao diretório: "terminal_data_folder\Include".
1.3 Entradas
Em seguida, são definidos o tipo, nome, valores padrão e um comentário. O papel deles é mostrado na figura 2.
input double MaximumRisk = 0.02; // Risco Máximo em porcentagem input double DecreaseFactor = 3; // Fator de Redução input int MovingPeriod = 12; // Período da Média Móvel input int MovingShift = 6; // Deslocamento da Média Móvel
Os parâmetros MaximumRisk e DecreaseFactor serão utilizados para a gestão de risco, enquanto MovingPeriod e MovingShift definem o período e o deslocamento do indicador Média Móvel que será usado para verificar as condições de negociação.
O texto do comentário na linha do parâmetro de entrada, juntamente com os valores padrão, aparecem na aba "Opções" em vez do nome do parâmetro de entrada:

Figura 2. Parâmetros de Entrada do EA de Média Móvel
1.4. Variáveis Globais
A variável global ExtHandle é declarada e será usada para armazenar o identificador do indicador Média Móvel.
//--- int ExtHandle=0;
Em seguida, temos seis funções. O propósito de cada uma delas é descrito no comentário antes do corpo da função:
- TradeSizeOptimized() - Calcula o tamanho ótimo do lote;
- CheckForOpen() - Verifica as condições para abrir uma posição;
- CheckForClose() - Verifica as condições para fechar uma posição;
- OnInit() - Função de inicialização do Expert;
- OnTick() - Função de execução em cada tick;
- OnDeinit() - Função de desinicialização do Expert;
As últimas três funções são funções de tratamento de eventos; as três primeiras são chamadas em seu código.
2. Funções de Tratamento de Eventos
2.1. A função OnInit()
A função OnInit() é chamada uma vez durante a primeira execução do Expert Advisor. Normalmente, no manipulador de eventos OnInit(), o EA é preparado para operação: os parâmetros de entrada são verificados, indicadores e parâmetros são inicializados, etc. Em caso de erros críticos, quando o funcionamento posterior é sem sentido, a função é encerrada com o código de retorno INIT_FAILED.
//+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Erro ao criar indicador MA"); return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); }
Como a negociação do EA se baseia no indicador Média Móvel, ao chamar iMA(), o indicador de Média Móvel é criado e seu identificador é salvo na variável global ExtHandle.
Em caso de erro, a função OnInit() é encerrada com o código de retorno INIT_FAILED - essa é a maneira correta de finalizar a operação do EA/indicador em caso de inicialização malsucedida.
2.2. A função OnTick()
A função OnTick() é chamada sempre que uma nova cotação é recebida para o símbolo do gráfico em que o EA está rodando.
//+------------------------------------------------------------------+ //| Função de execução em cada tick | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(PositionSelect(_Symbol)) CheckForClose(); else CheckForOpen(); //--- }
A função PositionSelect() é utilizada para verificar se existe uma posição aberta para o símbolo atual.
Se houver posições abertas, a função CheckForClose() é chamada, que analisa o estado atual do mercado e fecha a posição aberta; caso contrário, a função CheckForOpen() é chamada, que verifica as condições de entrada no mercado e abre uma nova posição se tais condições ocorrerem.
2.3. A função OnDeInit()
A função OnDeInit() é chamada quando um EA é removido do gráfico. Se um programa colocar objetos gráficos durante a operação, eles podem ser removidos do gráfico.
//+------------------------------------------------------------------+ //| Função de desinicialização do Expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+
Neste caso, nenhuma ação é executada durante a desinicialização do Expert Advisor.
3. Funções de Serviço
3.1. Função TradeSizeOptimized()
Essa função calcula e retorna o valor do tamanho ótimo do lote para abertura de posição com um nível de risco especificado e resultados de negociação.
//+------------------------------------------------------------------+ //| Calcular o tamanho ótimo do lote | //+------------------------------------------------------------------+ double TradeSizeOptimized(void) { double price=0.0; double margin=0.0; //--- Calcular o tamanho do lote if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price)) return(0.0); if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin)) return(0.0); if(margin<=0.0) return(0.0); double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)*MaximumRisk/margin,2); //--- calcular a série de perdas consecutivas if(DecreaseFactor>0) { //--- solicitar todo o histórico de negociações HistorySelect(0,TimeCurrent()); //-- int orders=HistoryDealsTotal(); // total de negócios int losses=0 // número de negócios com perdas na série for(int i=orders-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) { Print("HistoryDealGetTicket falhou, sem histórico de negociações"); break; } //--- verificando o símbolo do negócio if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue; //--- verificando o lucro double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); if(profit>0.0) break; if(profit<0.0) losses++; } //--- if(losses>1) lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1); } //--- normalizando e verificando os valores permitidos do volume de negociação double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); lot=stepvol*NormalizeDouble(lot/stepvol,0); double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); if(lot<minvol) lot=minvol; double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); if(lot>maxvol) lot=maxvol; //--- retornar o valor do volume de negociação return(lot); }
A função SymbolInfoDouble() é utilizada para verificar a disponibilidade de preços para o símbolo atual; em seguida, a função OrderCalcMargin() é usada para solicitar a margem necessária para colocar uma ordem (neste caso, uma ordem de compra). O tamanho inicial do lote é determinado a partir do valor da margem necessária para a colocação de uma ordem, a margem livre da conta (AccountInfoDouble(ACCOUNT_FREEMARGIN)) e o valor máximo de risco permitido especificado no parâmetro de entrada MaximumRisk.
Se o valor do parâmetro de entrada DecreaseFactor for positivo, negócios no histórico são analisados e o tamanho do lote é ajustado levando em consideração informações sobre a série máxima de operações perdedoras: o tamanho inicial do lote é multiplicado pelo tamanho (1-losses/DecreaseFactor).
Em seguida, o volume da operação é "arredondado" para um valor que é múltiplo do passo mínimo permitido de volume (stepvol) para o símbolo atual. Também são solicitados os valores mínimo (minvol) e máximo (maxvol) do volume de negociação, e se o valor do lote exceder os limites permitidos, ele é ajustado. Como resultado, a função retorna o valor calculado do volume de negociação.
3.2. Função CheckForOpen()
CheckForOpen() é utilizada para verificar as condições de abertura da posição e abri-la quando as condições de negociação ocorrerem (neste caso, quando o preço cruza a média móvel).
//+------------------------------------------------------------------+ //| Verificar condições para abrir posição | //+------------------------------------------------------------------+ void CheckForOpen(void) { MqlRates rt[2]; //--- copiar os valores de preço if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRates de ",_Symbol," falhou, sem histórico"); return; } //--- Negociar apenas no primeiro tick da nova barra if(rt[1].tick_volume>1) return; //--- Obter o valor atual do indicador Média Móvel double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer de iMA falhou, sem dados"); return; } //--- verificar os sinais ENUM_ORDER_TYPE signal=WRONG_VALUE; if(rt[0].open>ma[0] && rt[0].close<ma[0]) signal=ORDER_TYPE_SELL // condição de venda else { if(rt[0].open<ma[0] && rt[0].close>ma[0]) signal=ORDER_TYPE_BUY // condição de compra } //--- verificações adicionais if(signal!=WRONG_VALUE) if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) if(Bars(_Symbol,_Period)>100) { CTrade trade; trade.PositionOpen(_Symbol,signal,TradeSizeOptimized(), SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK), 0,0); } //--- }
Ao negociar utilizando a média móvel, você precisa verificar se o preço cruza a média móvel. Utilizando a função CopyRates(), dois valores dos preços atuais são copiados na estrutura de array rt[], rt[1] corresponde à barra atual, enquanto rt[0] - barra concluída.
Uma nova barra é iniciada verificando o volume de ticks da barra atual; se for igual a 1, então uma nova barra foi iniciada. Vale ressaltar que esse método de detectar uma nova barra pode falhar em alguns casos (quando as cotações chegam em lotes), então o fato de iniciar a formação de uma nova barra deve ser feito salvando e comparando o tempo da cotação atual (veja IsNewBar).
O valor atual do indicador de Média Móvel é solicitado usando a função CopyBuffer() e é salvo no array ma[] que contém apenas um valor. O programa então verifica se o preço cruzou a média móvel e realiza verificações adicionais (se a negociação utilizando o EA é possível e a presença de barras no histórico). Se for bem-sucedido, uma posição apropriada para o símbolo é aberta chamando o método PositionOpen() do objeto de negociação (uma instância da CTrade).
O preço de abertura da posição é definido utilizando a função SymbolInfoDouble() que retorna o preço Bid ou Ask dependendo do valor da variável signal. O volume da posição é determinado chamando TradeSizeOptimized() descrito acima.
3.3. Função CheckForClose()
CheckForClose() verifica as condições para fechamento de posição e a fecha se as condições para fechamento ocorrerem.
//+------------------------------------------------------------------+ //| Verificar condições para fechar posição | //+------------------------------------------------------------------+ void CheckForClose(void) { MqlRates rt[2]; //--- Copiar valores de preço if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRates de ",_Symbol," falhou, sem histórico"); return; } //--- Negociar apenas no primeiro tick da nova barra if(rt[1].tick_volume>1) return; //--- obter o valor atual do indicador Média Móvel double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer de iMA falhou, sem dados"); return; } //--- obter o tipo da posição selecionada anteriormente usando PositionSelect() bool signal=false; long type=PositionGetInteger(POSITION_TYPE); if(type==(long)POSITION_TYPE_BUY && rt[0].open>ma[0] && rt[0].close<ma[0]) signal=true; if(type==(long)POSITION_TYPE_SELL && rt[0].open<ma[0] && rt[0].close>ma[0]) signal=true; //--- verificações adicionais if(signal) if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) if(Bars(_Symbol,_Period)>100) { CTrade trade; trade.PositionClose(_Symbol,3); } //--- }
O algoritmo da função CheckForClose() é semelhante ao algoritmo da CheckForOpen(). Dependendo da direção das posições abertas atuais, as condições de fechamento são verificadas (preço cruzando a MA para baixo para compras ou para cima para vendas). Uma posição aberta é fechada chamando o método PositionClose() do objeto de negociação (instância da CTrade).
4. Teste de Estratégia
Os melhores valores dos parâmetros podem ser encontrados utilizando o Teste de Estratégia do terminal MetaTrader 5.
Por exemplo, ao otimizar o parâmetro MovingPeriod no intervalo de 01/01/2012 a 01/08/2013, os melhores resultados são obtidos com MovingPeriod=45:

Resultados do Teste de Backtesting do Expert Advisor de Média Móvel
Conclusões:
O Expert Advisor de Média Móvel incluído no pacote padrão do MetaTrader 5 é um exemplo de uso de indicadores técnicos, funções de histórico de operações e classes de negociação da Biblioteca Padrão. Além disso, o EA inclui um sistema de gestão de risco baseado nos resultados das operações.
Publicações relacionadas
- MACD Sample: Um Guia Prático para o Expert Advisor no MetaTrader 5
- Trader Automático: RSI e MFI com Otimização Automática para MetaTrader 4
- Combo Trader: O EA Atualizado para MetaTrader 4
- Fechamento Automático com Kijun Sen: Um EA para MetaTrader 4
- GBP9AM: O EA Para MetaTrader 5 Que Você Precisa Conhecer