English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Introdução ao MQL5: Como escrever Expert Advisor e Custom Indicator simples

Introdução ao MQL5: Como escrever Expert Advisor e Custom Indicator simples

MetaTrader 5Exemplos | 23 dezembro 2013, 16:21
12 542 0
Denis Zyatkevich
Denis Zyatkevich

Introdução

MetaQuotes Programming Language 5 (MQL5), incluído no Terminal Cliente do MetaTrader 5, tem muitas novas possibilidades e um maior desempenho, em comparação com MQL4. Este artigo irá ajudá-lo a se familiarizar com esta nova linguagem de programação. Os exemplos simples de como escrever um Expert Advisor e indicador personalizado são apresentados neste artigo. Vamos também considerar alguns detalhes da linguagem MQL5 que são necessários para entender estes exemplos.

Os detalhes do artigo e a descrição completa de MQL5 podem ser encontrados em Referência MQL5, incluído no MetaTrader 5. Informações contidas na ajuda interna do MQL5 são suficientes para estudar a linguagem. Este artigo pode ser útil para aqueles que estão familiarizados com MQL4 e também para iniciantes, que estão apenas começando a programar sistemas de negociações e indicadores.


Introdução ao MQL5

A plataforma de negociação MetaTrader 5 permite que você faça análises técnicas de instrumentos financeiros e negociações, tanto manualmente como em modo automático. MetaTrader 5 difere de seu antecessor - MetaTrader 4. Particularmente, os conceitos de negócio, posição e ordem foram aperfeiçoados.

  • Posição - é um compromisso de mercado, o número de contratos de instrumentos financeiros comprados ou vendidos.
  • Ordem - é uma ordem para comprar ou vender uma certa quantidade do instrumento financeiro sob determinadas condições.
  • Negociação - é uma execução de ordem pelo corretor, o que leva à abertura,modificação ou fechamento de uma posição.

O Terminal de cliente possui uma linguagem de programação interna chamado MQL5, que lhe permite escrever vários tipos de programas com finalidades diferentes:

  • Expert Advisor - é um programa que negocia de acordo com algum algoritmo específico. Um Expert Advisor permite que você implemente um sistema de negocio para a negociação automatizada (operações que podem ser executadas sem um trader). Um Expert Advisor pode realizar operações de negócio, abrir e fechar posições, e gerenciar ordens pendentes.
  • Indicador - é um programa que permite apresentar dados em forma gráfica, que é conveniente para a análise.
  • Script - é um programa que permite realizar uma sequência de operações de uma só vez.

Expert Advisors, Indicadores e Scripts podem chamar funções da biblioteca padrão MQL5 e funções DLL, incluindo as bibliotecas de operação do sistema. Os trechos de código, localizado em outros arquivos, podem ser incluídos no texto do programa, escrito em MQL5.

Para escrever um programa (Expert Advisor, Indicador ou Script), você deve iniciar o Terminal Cliente MetaTrader 5 e escolher o Editor de Linguagem MetaQuotes no menu Ferramentas, ou simplesmente pressionar a tecla F4.

Figura 1. Iniciando o MetaEditor.

Na janela do MetaEditor 5 escolha Novo no menu Arquivo ou pressione Ctrl+N.

Figura 2. Criando um novo Programa.

Na janela do Assistente MQL5 escolha o tipo de programa que você deseja criar: 

Figura 3. Assistente MQL5.

Na etapa seguinte, você deve especificar o nome do programa, informações sobre o autor e parâmetros, que serão solicitados pelo usuário após a execução do programa.

Figura 4. Propriedades gerais de Expert Advisor.

Depois disso, o modelo do programa (Expert Advisor, Indicador or Script), que você pode editar e preencher com o seu código, será criado:

Figura 5. Modelo de um novo programa.

Quando o programa estiver pronto, é necessário compilá-lo. Para compilar o programa, escolha Compilar do menu Arquivo ou pressione a tecla F7:

Figura 6. Compilação do programa.

Se não houver nenhum erro no código do programa, será criado um arquivo com extensão .ex5. Depois disso, você pode anexar este novo Expert Advisor, Script ou Indicador para o gráfico do Terminal Cliente MetaTrader 5 para executá-lo.

Um programa em MQL5 é uma sequência de operadores. Cada operador termina com um símbolo de ponto e vírgula ";". Para sua conveniência, você pode adicionar comentários em seu código, que são colocados dentro do símbolo "/*" e "*/", ou após "//" até o fim da linha. MQL5 é uma linguagem de programação "orientada a evento". Isto significa que, quando certos eventos (início ou término do programa, chegada de novas cotações, etc.) acontecem, o Terminal Cliente executa a correspondente função (subprograma), escrito pelo usuário, que realiza operações específicas. O Terminal Cliente possui os seguintes eventos predefinidos:

  • Start o evento ocorre quando o Script é executado (usado apenas em Scripts). Ele leva para a execução da função OnStart. Equivalente no MQL4 - função start em Scripts.
  • Init o evento acontece quando o Expert Advisor ou indicador é executado. Ele leva para a execução da função OnInit. Equivalente no MQL4 - função init.
  • Deinit o evento acontece quando o Expert Advisor ou o indicador é encerrado (por exemplo, após a desanexação no gráfico, fechando o Terminal do cliente, etc.). Ele leva para a execução da função OnDeinit. Equivalente no MQL4 - função deinit.
  • NewTick o evento acontece quando chegam novas cotações para o instrumento financeiro atual (usado somente em Expert Advisors). Ele leva para a execução da função OnTick. Equivalente no MQL4 - função start em Expert Advisors.
  • Calculate o evento ocorre quando o Indicador é iniciado (após a execução da função OnInit) e quando chegam novas cotações para o instrumento financeiro atual (usado somente em Indicadores). Ele leva para a execução da função OnCalculate. Equivalente no MQL4 - função start em Indicadores.
  • Trade o evento acontece quando a ordem é executada, modificada ou excluída, ou quando uma posição é aberta, modificada ou fechada (usada somente em Expert Advisors). Ele leva para a execução da função OnTrade. Em MQL4 não existe um equivalente deste evento e função.
  • BookEvent o evento acontece quando a Profundidade de Mercado é alterada (usada apenas em Expert Advisors). Ele leva para a execução da função OnBookEvent. Em MQL4 não existe um equivalente deste evento e função, assim como a Profundidade de Mercado.
  • ChartEvent o evento ocorre quando o usuário trabalha com o gráfico: cliques de mouse e teclas pressionadas, quando a janela do gráfico está em foco. Também acontece durante a criação, movimento ou exclusão de objetos do gráfico, etc. (usado em Expert Advisors e Indicadores). Ele leva para a execução da função OnChartEvent. Não existe equivalente deste evento e função em MQL4.
  • Timer o evento acontece periodicamente quando o timer é acionado, caso tenha sido ativado pela função EventSetTimer. Ele leva para a execução da função OnTimer. Em MQL4 não existe equivalente deste evento e função, bem como um timer.

Antes de usar as variáveis, é necessário especificar o tipo de dados de cada uma delas. MQL5 oferece suporte a mais tipos de dados do que o MQL4:

  • bool destina-se a armazenar valores lógicos (verdadeiro ou falso). Leva 1 byte de memória.
  • char destina-se a armazenar valores inteiros de -128 a 127. Leva 1 byte de memória.
  • uchar destina-se a armazenar valores de inteiros sem sinal de 0 a 255. Leva 1 byte de memória.
  • short destina-se a armazenar valores de -32 768 a 32 767. Leva 2 bytes de memória.
  • ushort destina-se a armazenar valores de inteiros sem sinal de 0 a 65 535. Leva 2 bytes de memória.
  • int destina-se a armazenar valores de inteiros -2 147 483 648 a 2 147 483 647. Leva 4 bytes de memória.
  • uint destina-se a armazenar valores de inteiros sem sinal de 0 a 4 294 967 295. Leva 4 bytes de memória.
  • long destina-se a armazenar valores de inteiros de -9 223 372 036 854 775 808 a 9 223 372 036 854 775 807. Leva 8 bytes de memória.
  • ulong destina-se a armazenar valores de inteiros sem sinal de 0 a 18 446 744 073 709 551 615. Leva 8 bytes de memória.
  • float destina-se a armazenar valores Reais (ou tipos de ponto flutuante). Leva 4 bytes de memória.
  • double destina-se a armazenar valores Reais (ou tipos de ponto flutuante). Geralmente é usado para armazenar dados de preço. Leva 8 bytes de memória.
  • datetime destina-se a armazenar valores de data e hora, que é um número de segundos decorrido desde 01.01.1970 00:00:00. Leva 8 bytes de memória.
  • color destina-se a armazenar informações sobre a cor, contendo as características dos três componentes de cor - vermelho, verde e azul. Leva 4 bytes de memória.
  • enum representa a enumeração. Ele permite especificar um tipo determinado de um conjunto limitado de dados Leva 4 bytes de memória.
  • string destina-se a armazenar sequências de caracteres de texto Sua representação interna é uma estrutura de 8 bytes, que contêm o tamanho do buffer com a sequência de caracteres e o ponteiro para este buffer.

É necessário escolher o tipo de dados apropriado para o desempenho ideal e o uso racional de memória. Em MQL5 há um novo conceito chamado structure. Esta estrutura combina dados que estão logicamente relacionados.

Sistema de Negociação

O sistema de negociação, que é usado neste artigo como um exemplo, baseia-se na hipótese de que as instituições financeiras Europeias são abertas na parte da manhã, e depois, os eventos econômicos são publicados nos EUA, o que leva à uma tendência para o EURUSD. O período do gráfico não é importante, mas é recomendado usar o intradiário,pois o diário (ou parte) é visível somente uma vez, por isso ele é mais conveniente para a observação.

Figura 7. Sistema de Negociação.

A 07h da manhã (hora do servidor) ordens pendentes de Buy Stop e Sell Stop são colocadas à distância de um ponto além da faixa de preço do dia atual. Para ordens pendentes de Buy Stop o spread é levado em conta. Os níveis de StopLoss (Perda máxima) são colocados em lados opostos do intervalo. Após sua execução, a ordem de StopLoss (Perda máxima) é substituída por uma média móvel simples, mas somente se esta for lucrativa.

O benefício deste tipo de rastreio,comparado ao clássico Trailing Stop, é os seguinte: ele permite evitar o encerramento antecipado de posições no caso de haver picos de preços com correções. Por outro lado leva ao fechamento da posição quando a tendência termina e começa o movimento lateral. A média móvel simples é calculada usando os dados do gráfico de minuto e possui uma média de período igual a 240.

O nível de lucro depende da volatilidade do mercado atual. Para determinar a volatilidade do mercado, o indicador Average True Range (ATR) é aplicado ao gráfico diário, com período igual a 5. Assim, ele nos mostra a média do intervalo diário da semana passada. Para determinar o valor de TakeProfit (Lucro máximo) para a posição comprada, vamos somar o valor do indicador ATR com o mínimo do preço diário atual. O mesmo acontece para posições vendidas: vamos subtrair o valor do indicador ATR com o máximo do preço diário atual. A ordem não é colocada caso o valor do preço da ordem esteja além dos níveis de StopLoss (Perda máxima) ou TakeProfit (Lucro máximo). Após as 19h (hora do servidor) todas as ordens pendentes são excluídas, não sendo executadas neste dia (as posições em aberto estarão sendo obeservadas pelo Trailing Stop até seu fechamento).


Escrevendo um indicador

Vamos escrever um indicador, que nos mostre os níveis lucro do sistema de negociação descrito acima.

Se o primeiro símbolo da linha é "#", significa que essa cadeia de caracteres é uma diretiva de um pré-processador. Diretivas são usadas para especificar propriedades adicionais ao programa, para declarar constantes, incluir arquivos de cabeçalho e funções importadas. Note que após as diretivas de pré-processador, não há símbolos de ponto e vírgula (;).

#property copyright   "2010, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property description "Este indicador calcula os níveis de TakeProfit(Lucro máximo) "
#property description "usando a volatilidade média do mercado. Ele usa os valores"
#property description "do indicador Average True Range (ATR), calculado"
#property description "sobre os dados de preço diário. Os valores do indicador são calculados"
#property description "usando os valores de máximo e mínimo do preço no diário."
#property version     "1.00"

As informações sobre o autor e sua página da web podem ser especificadas nas propriedades de copyright e link , a propriedade description permite que você adicione uma breve descrição, a propriedade version permite que você especifique a versão do programa. Quando um indicador está executando esta informação parece como se segue:

Figura 8. Iformações do indicador.

É necessário especificar a posição dos indicadores: em um gráfico ou em uma janela separada. Isso pode ser feito especificando uma das propriedades: indicator_chart_window ou indicator_separate_window:

#property indicator_chart_window

Além disso, você precisa especificar o número de buffers do indicador e o número de série do gráfico que será usado. Em nosso caso, existem duas linhas, cada uma delas possui seu próprio buffer - um array com os dados que serão plotados.

#property indicator_buffers 2
#property indicator_plots   2

Para cada linha do indicador vamos especificar as seguintes propriedades: tipo (propriedade indicator_type ), cor (propriedade indicator_color ), estilo do desenho (propriedade indicator_style ) e o rótulo de texto (propriedade indicator_label ):

#property indicator_type1   DRAW_LINE
#property indicator_color1  C'127,191,127'
#property indicator_style1  STYLE_SOLID
#property indicator_label1  "Buy TP"
#property indicator_type2   DRAW_LINE
#property indicator_color2  C'191,127,127'
#property indicator_style2  STYLE_SOLID
#property indicator_label2  "Sell TP"

Os tipode de linhas básicas são: DRAW_LINE - para linhas, DRAW_SECTION - para seções, DRAW_HISTORAM para histogramas. Existem muitos outros estilos de desenho. Você pode definir a cor, especificando o brilho de seus três componentes RGB ou usando as cores predefinidas, por exemplo, vermelho, verde, azul, branco, etc. Os estilos de linha são: STYLE_SOLID - linha sólida, STYLE_DASH - linha tracejada, STYLE_DOT - linha pontilhada, STYLE_DASHDOT - linha ponto tracejada, STYLE_DASHDOTDOT - linha tracejada de dois pontos.

Figura 9. Descrição das  linhas do indicador.

Usando o modificador de entrada input, vamos especificar as variáveis externas (você pode especificar seus valores após o início do indicador), seus tipos e valores padrão:

input int             ATRper       = 5;         //Período do ATR
input ENUM_TIMEFRAMES ATRtimeframe = PERIOD_D1; //timeframe (periodicidade) do indicador

Os nomes dos parâmetros podem ser especificados nos comentários - eles estarão visíveis em vez dos nomes de variáveis:

Figura 10. Parâmetros de entrada do indicador.

Em um nível global (que é visível para todas as funções), nós vamos especificar variáveis (e seus tipos), que serão usadas por diferentes funções em nosso indicador.

double bu[],bd[];
int hATR;

Os arrays bu[] e bd[] serão usadas para as linhas superiores e inferiores do indicador. Nós usaremos arrays dinâmicos (isto é, arrays sem um número específico de elementos), porque não sabemos exatamente o número de elementos que serão usados (seu tamanho será alocado automaticamente). O manipulador do indicador técnico interno será armazenado na variável hATR. É necessário utilizar o manipulador de indicador para usar o indicador.

A função OnInit é chamada depois que o indicador está em execução (após ser anexado ao gráfico)

void OnInit()
  {
   SetIndexBuffer(0,bu,INDICATOR_DATA);
   SetIndexBuffer(1,bd,INDICATOR_DATA);
   hATR=iATR(NULL,ATRtimeframe,ATRper);
  }

A função SetIndexBuffer se faz necessária para especificar que os arrays bu[] e bd[] são buffers do indicador, que serão usados para armazenar os valores dos indicadores, que são plotados como linhas do indicador. O primeiro parâmetro define o índice do buffer do indicador, com ordem começando a partir de 0. O segundo parâmetro especifica uma array, atribuído ao buffer do indicador. O terceiro parâmetro especifica o tipo de dados, armazenado no buffer de indicador: INDICATOR_DATA - dados para plotagem, INDICATOR_COLOR_INDEX - cor do desenho, INDICATOR_CALCULATIONS - buffers auxiliares para cálculos intermediários.

Manipulador do indicador, retornado pela função iATR, é armazenada na variável hATR. O primeiro parâmetro da função iATR é o símbolo de negociação, NULL - é o símbolo do gráfico atual. O segundo parâmetro especifica o período gráfico, que é usado para o cálculo do indicador. O terceiro parâmetro é o período médio do indicador ATR .

A função OnCalculate é chamada logo após o fim da execução da função OnInit e sempre após a chegada de uma nova cotação para o símbolo atual. Há duas maneiras de chamar essa função. Uma delas, usado em nosso indicador, parece como se segue:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])

Após a chamada da função OnCalculate, o Terminal Cliente passa os seguintes parâmetros:  rates_total - número de barras do gráfico atual, prev_calculated  - número de barras, já calculado pelo indicador, time[], open[], high[], low[], close[], tick_volume[], volume[], spread[] - arrays contendo os valores de tempo, abertura, máximo, mínimo, fechamento, volume por tick, volume e spread de cada barra, respectivamente. Para reduzir o tempo de cálculo, não é necessário recalcular os valores do indicador, que já foram calculados e não sofreram mudanças. Depois de chamar a função OnCalculate, ela retorna o número de barras que já foram calculados.

O código da função OnCalculate está entre parênteses. Começa com variáveis locais, que são usadas na função - seus tipos e nomes.

  {
   int i,day_n,day_t;
   double atr[],h_day,l_day;

A variável i é usada como um contador de ciclo, as variáveis day_n e day_t são usadas para armazenar o número de dias e para armazenar temporariamente o número de dias, quando calculado os valores de preço máximo e mínimo durante o dia. O array atr[] é usado para armazenar os valores do indicador ATR , e as variáveis h_day e l_day são usadas para armazenar os valores de preço máximo e mínimo durante o dia.

Primeiro, temos que copiar os valores do indicador ATR para o array atr[] , utilizando a função CopyBuffer. Vamos utilizar o manipulador do indicador ATR  como primeiro parâmetro desta função. O segundo parâmetro é o número de buffers do indicador (numeração começa com 0), o indicador ATR possui um único buffer. O terceiro parâmetro especifica o número do primeiro elemento para começar, a indexagem é executada do presente para o passado, o elemento zero corresponde a barra de cotação atual (incompleta). O quarto parâmetro especifica o número de elementos, que deve ser copiado.

Vamos copiar dois elementos, pois estamos interessados apenas no penúltimo elemento, o que corresponde a última barra (concluída). O último parâmetro é o array de destino para copiar os dados.

   CopyBuffer(hATR,0,0,2,atr);

A direção de indexagem do array depende da sinalização AS_SERIES . Se ela estiver definida (ou seja, igual a true), o array é considerado como timeseries, a indexação dos elementos é executada a partir dos dados mais recentes para os mais antigos. Se ela não foi definida (ou seja, igual a false), os elementos mais velhos possuem um índice mais baixo, enquanto que os elementos mais novos possuem um índice maior.

   ArraySetAsSeries(atr,true);

Para o array atr[] nos definimos para que o sinalizador AS_SERIES seja true usando a função ArraySetAsSeries (o primeiro parâmetro desta função é o array, para que o sinalizador deva ser alterada, o segundo parâmetro é o novo valor do sinalizador). Agora, o índice da barra atual (incompleta) é igual a 0, o índice da penúltima barra (completa) é igual a 1.

O operador for permite a criação de um loop (repetição).

   for(i=prev_calculated;i<rates_total;i++)
     {
      day_t=time[i]/PeriodSeconds(ATRtimeframe);
      if(day_n<day_t)
        {
         day_n=day_t;
         h_day=high[i];
         l_day=low[i];
        }
        else
        {
         if(high[i]>h_day) h_day=high[i];
         if(low[i]<l_day) l_day=low[i];
        }
      bu[i]=l_day+atr[1];
      bd[i]=h_day-atr[1];
     }

Após o operador for o primeiro operador entre parenteses é uma declaração: i=prev_calculated. Em seguida, há uma expressão, que em nosso caso é: i<rates_total. É uma condição de loop (repetição) - o loop (repetição) é executado enquanto for verdade. A terceira é uma declaração que é executado após cada execução do loop (repetição). Em nosso caso, é  i++ (é igual a i=i+1 e significa aumentar o valor da variável i por 1).

Em nosso loop (repetição), a variável i varia do valor de prev_calculated para o valor igual a rates_total-1 com incremento de 1. O arrays de dados históricos como (time[], high[] and low[]) por padrão, não estao em timeseries, o índice 0 corresponde a barra mais antiga do histórico, já o último corresponde a barra atual, incompleta. Em loop (repetição) são processadas todas as barras não calculadas, da primeira (prev_calculated) a última barra (rates_total-1). Para cada uma destas barras, calculamos os valores de nosso indicador.

Os valores de tempo do array time[] são armazenados como número em segundos , decorrido desde 01.01.1970 00:00:00. Se dividirmos pelo número de segundos em um dia (ou em algum outro período), a parte inteira do resultado será o número do dia iniciado em 01.01.1970 (ou algum outro período de tempo). A função PeriodSeconds retorna o número de segundos em um período de tempo, que é definido como um parâmetro. A variável day_t é o número do dia, correspondente à barra com o índice i. A variável day_n é o número do dia, para o qual são calculados os valores de máximo e mínimo do preço.

Vamos considerar o operador if. Se a expressao entre parêntesis do operador for verdadeira, então, o operador if, seguido pela palavra-chave, é executado. Se for falso, então, o operador else, seguido pela palavra-chave, é executado. Cada operador pode ser composto, ou seja, podem consistir de vários operadores, em nosso caso, eles estão entre parênteses.

Os valores de máximo e mínimo do preço diário processados são armazenados nas variáveis h_day e l_day , respectivamente. Em nosso caso, estamos verificando a seguinte condição: se a barra analisada corresponde ao novo dia, começamos a calcular os valores de máximo e mínimo do preço novamente, caso contrário continuamos. Nós estamos calculando os valores para cada linha do indicador: para a linha superior - nós estamos usando o preço diário mínimo, para a linha inferior - estamos usando os valores de máximo do preço.

No fim da função OnCalculate, o operador return retorna o número de barras calculadas.

   return(rates_total);
  }

Dentro da função DeInit (ela opera quando o indicador é removido do gráfico ou quando o Terminal Cliente é fechado) ,a memória, reservada pelo indicador ATR, é liberada utilizando a função IndicatorRelease. A função possui apenas um parâmetro - o manipulador do indicador.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hATR);
  }

Agora, nosso indicador está completo. Para compilá-lo, no MetaEditor escolha Compilar no menu Arquivo ou pressione a tecla F7. Se não houver nenhum erro no código, a compilação será bem sucedida. Os resultados da compilação são impressos na guia Erros da janela Caixa de ferramentas. Em nosso caso, o compilador pode imprimir o aviso de "Conversion possible loss of data" para a seguinte sequência:

      day_t=time[i]/PeriodSeconds(ATRtimeframe);

Nesta linha estamos descartando intencionalmente a parte fracionária, então, esta perda de dados não é um erro.

Quando um indicador está completo e é compilado, ele pode ser anexado aos gráficos no Terminal Cliente do MetaTrader 5 ou pode ser usado em outros indicadores, Expert Advisors ou Scripts. O código-fonte deste indicador está disponível em anexo neste artigo.


Escrevendo um Expert Advisor

Agora está na hora de escrever um Expert Advisor, que implementa o sistema de negócio descrito acima Nós estamos assumindo que ele vai negociar apenas um instrumento financeiro. Para que vários Expert Advisors negocie um único instrumento, é preciso cuidado para analisar a contribuição de cada um deles em uma situação geral, isto está além do escopo deste artigo.

#property copyright   "2010, MetaQuotes Software Corp."
#property version     "1.00"
#property description "Este Expert Advisor coloca as ordens pendentes durante o"
#property description "intervalo de tempo entre StartHour até EndHour sobre os níveis de preços, que"
#property description "são 1 ponto abaixo/mínimo do intervalo de negócio atual."
#property description "O níveis de StopLoss são colocados no lado oposto"
#property description "do intervalo de preço. Após a execução da ordem, o valor de TakeProfit"
#property description "é definido no nível do 'indicator_TP'. O nível de StopLoss é alterado"
#property description "para os valores da média móvel simples (SMA) somente para ordens com lucro."

A finalidade destas diretivas de pré-processador já foram consideradas na seção Escrevendo um Indicador. Eles funcionam da mesma maneira para um Expert Advisor.

Vamos especificar os valores dos dos parâmetros de entrada (que pode ser definido pelo usuário depois de iniciar um Expert Advisor), seus tipos e valores padrão.

input int    StartHour = 7;
input int    EndHour   = 19;
input int    MAper     = 240;
input double Lots      = 0.1;

Os parâmetros StartHour e EndHour definem o período de tempo (horas de inicio e fim) para ordens pendentes. O parâmetro MAper define o período médio da média móvel simples, que é utilizada para o nível de StopLoss para a posição em aberto durante seu rastreio (Trailing Stop). O parâmetro Lots define o volume do instrumento financeiro, usado na negociação.

Vamos especificar as variáveis globais, que serão usadas nas diferentes funções de negócio:

int hMA,hCI;

A variável hMA será usada para armazenar o manipulador do indicador e a variável hCI será usada para armazenar o manipulador do indicador personalizado (é o idicador, que foi escrito acima).

A função OnInit é executada quando o Expert Advisor é iniciado.

void OnInit()
  {
   hMA=iMA(NULL,0,MAper,0,MODE_SMA,PRICE_CLOSE);
   hCI=iCustom(NULL,0,"indicator_TP");
  }

Nesta função temos os manipuladores do indicador média móvel e de nosso indicador personalizado. A função iMA e seus parâmetros são usados do mesmo modo que na função iATR , descrito acima.

O primeiro parâmetro da função iCustom é o nome simbólico do instrumento, NULL - significa o instrumento do gráfico atual. O segundo parâmetro - é o timeframe (periodicidade) do gráfico, na qual os dados são usados para calcular o indicador, 0 - significa o período do gráfico atual. O terceiro parâmetro é o nome do indicador (sem extensão). O caminho do arquivo é relativo à pasta MQL5\Indicators\.

Vamos criar a função OnTick, que é executada após a chegada de uma nova cotação:

void OnTick()
  {

O código da função é colocado entre parênteses.

Vamos especificar a estrutura de dados predefinida, que será usada no Expert Advisor:

   MqlTradeRequest request;
   MqlTradeResult result;
   MqlDateTime dt;
A estrutura predefinida MqlTradeRequest possui ordens e parâmetros de posições, que são passados para a função OrderSend na operação de negócio. O propósito da estrutura MqlTradeResult é para armazenar informações a respeito dos resultados das operações de negócio, retornada pela função OrderSend. O propósito da estrutura predefinida MqlDateTime é para armazenar as informações de hora e data.

Vamos especificar as variáveis locais (e seus tipos) que serão usados na função OnTick:

   bool bord=false, sord=false;
   int i;
   ulong ticket;
   datetime t[];
   double h[], l[], ma[], atr_h[], atr_l[],
          lev_h, lev_l, StopLoss,
          StopLevel=_Point*SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL),
          Spread   =NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK) - SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits);
As variáveis booleanas bord e sord são usadas como sinalizadoes, que mostram se há ordens pendentes de Buy Stop e Sell Stop. Se existe, a variável correspondente possui valor igual a true, caso contrário igual a false. A variável i é usada para contar as operações de loop (repetição) e para armazenar dados intermediários. O ingresso de ordens pendentes é armazenado na variável ticket.

Os arrays t[], h[] e l[] são utilizados para armazenar tempo, os valores de máximo e mínimo de preço para cada barra de dados históricos. O array ma[] é utilizado para armazenar os valores do indicador média móvel, os arrays atr_h[] e atr_l[] são usados para armazenar os valores das linhas superior e inferior do indicador personalizado indicator_TP, que nós criamos.

As variáveis lev_h e lev_l são usadas para armazenar os valores máximos e mínimos de preço do dia atual e dos preços de abertura das ordens pendentes. A variável StopLoss é usada para armazenar temporariamente o preço de Stop Loss das posições em aberto.

A variável StopLevel é utilizada para armazenar o valor de STOP_LEVEL - a distância mínima entre o preço atual e o preço da ordem pendente (em unidades de preço). Nós calculamos este valor como um produto do ponto preço (a variável predefinida _Point) pelo valor da variável STOP_LEVEL, definida em pontos. O valor do STOP_LEVEL é retornado pela função SymbolInfoInterger. O primeiro parâmetro desta função é o nome do símbolo, o segundo - é a propriedade solicitada do identificador. O símbolo (nome do instrumento financeiro) pode ser obtido usando a função Symbol (ela não possui parâmetros).

O valor do Spread é usado para armazenar o valor do spread (em unidades de preço). Seu valor é calculado como a diferença entre o valor atual de compra (Ask) e de venda (Bid), normalizado utilizando a função NormalizeDouble. O primeiro parâmetro desta função é o valor do tipo double para normalizar, o segundo é o número de dígitos após o ponto, que obtivemos a partir da variável predefinida _Digits. Os valores atuais de compra (Ask) e venda (Bid) podem ser obtidos usando a função SymbolInfoDouble. O primeiro parâmetro desta função é o nome do símbolo, o segundo - é o identificador de propriedade.

Vamos preencher a estrutura request com valores, que será comum para todas as chamadas da função OrderSend:

   request.symbol      =Symbol();
   request.volume      =Lots;
   request.tp          =0;
   request.deviation   =0;
   request.type_filling=ORDER_FILLING_FOK;
O elemento request.symbol contém o nome simbólico do instrumento, que ele negocia, o elemento request.volume -o volume (tamanho do contrato) do instrumento financeiro, request.tp - o valor numérico de TakeProfit (em alguns casos não vamos usá-lo e preencheremos com 0), o request.deviation - permite desvios de preço durante a execução da operação de negócio, o request.type_filling - o tipo de ordem, podendo ser um dos seguintes::
  • ORDER_FILLING_FOK - o negócio poderá ser executado somente se o volume for igual ou maior do que o especificado em ordem.Se o volume não for suficiente, a ordem não será executada.
  • ORDER_FILLING_IOC - quando não há volume suficiente, a ordem será executada no volume máximo de mercado disponível.
  • ORDER_FILLING_RETURN - o mesmo que ORDER_FILING_IOC, mas neste caso uma ordem será adicionada para o volume que faltou.

Vamos obter o horário atual dos servidores (hora da última cotação) utilizando a função TimeCurrent. O único parâmetro desta função é o ponteiro apontando para a estrutura como resultado.

   TimeCurrent(dt);

Para todos os nossos cálculos, precisamos dos dados históricos de preço apenas do dia atual. O número de barras, que é necessário (com alguma reserva) pode ser calculado utilizando a fórmula: i = (dt.hour + 1)*60, onde dt.hour - é o elemento de estrutura, contendo a hora atual. Os valores de tempo, preço máximo e mínimo são copiados aos arrays t[], h[] e l[] utilizando as funções CopyTime, CopyHigh, CopyLow , respectivamente:

   i=(dt.hour+1)*60;
   if(CopyTime(Symbol(),0,0,i,t)<i || CopyHigh(Symbol(),0,0,i,h)<i || CopyLow(Symbol(),0,0,i,l)<i)
     {
      Print("Can't copy timeseries!");
      return;
     }

O primeiro parâmetro nas funções CopyTime, CopyHigh e CopyLow - é o nome do símbolo, o segundo - é o timeframe (periodicidade) do gráfico, o terceiro - é o elemento inicial para copiar, o quarto - é o número de elementos para copiar, o quinto - é o array de destino para os dados. Cada uma destas funções retorna o número de elementos copiados ou um valor negativo igual a -1 em caso de erro.

O operador if é utilizado para verificar o número de elementos copiados para todos os três arrays. Se o número de elementos copiados for menor que o necessário para cálculo (mesmo que para um dos arrays), ou para o caso de erro, ele imprime a mensagem "Impossível copiar timeseries!" no registro dos Experts e termina a execução da função OnTick utilizando o operador de retorno return. A mensagem é impressa na função Print. Ele pode imprimir quaisquer tipos de dados, os quais são separados por vírgula.

Quando os dados de preço forem copiados para os arrays t[], h[] e l[], nós definimos a sinalização AS_SERIES para true, utilizando a função ArraySetAsSeries, que foi considerada acima. É necessário definir a indexação do array como timeseries (a partir dos preços atuais aos preços antigos):

   ArraySetAsSeries(t,true);
   ArraySetAsSeries(h,true);
   ArraySetAsSeries(l,true);

Os valores de preço máximo e mínimo do dia corrente são colocados nas variáveis lev_h e lev_I:

   lev_h=h[0];
   lev_l=l[0];
   for(i=1;i<ArraySize(t) && MathFloor(t[i]/86400)==MathFloor(t[0]/86400);i++)
     {
      if(h[i]>lev_h) lev_h=h[i];
      if(l[i]<lev_l) lev_l=l[i];
     }

O loop é executado somente se a condição MathFloor(t[i]/86400) == MathFloor(t[0]/86400) for verdadeira, a fim de restringir a pesquisa com as barras pertencendo ao dia atual. Do lado esquerdo desta expressão há um número de barras do dia atual, à direita - o número do dia atual (86400 é o número de segundos em um dia). A função MathFloor arredonda o valor numérico, ou seja, ela utiliza apenas a parte inteira dos valores positivos. O único parâmetro desta função é a expressão para arredondamento. Em MQL5, também como em MQL4, a igualdade é definida com os símbolo "==" ( ver em Operações de relações).

Os preços de ordem são calculados como segue: para a ordem pendente do tipo Buy Stop nós adicionamos um ponto (a variável predefinida _Point é igual ao tamanho do ponto em unidades de preço) e o Spread para a variável lev_h (lev_h+=Spread+_Point ou lev_h=lev_h+Spread+_Point). Para as ordens pendentes do tipo Sell Stop, nós subtraimos um ponto do valor da variável lev_l (lev_l-=_Point ou lev_l=lev_l-_Point).

   lev_h+=Spread+_Point;
   lev_l-=_Point;

A seguir, copiaremos os valores dos buffers do indicador para arrays utilizando a função CopyBuffer. Os valores da média móvel são copiados para o array ma[], os valores da linha superior de nosso indicador personalizado são copiados para o array atr_h[], os valores da linha inferior do indicador são copiados para o array atr_I[]. A função CopyBuffer foi descrita acima, quando consideramos os detalhes do indicador.

   if(CopyBuffer(hMA,0,0,2,ma)<2 || CopyBuffer(hCI,0,0,1,atr_h)<1 || CopyBuffer(hCI,1,0,1,atr_l)<1)
     {
      Print("Impossível copiar o buffer de indicador!");
      return;
     }

Nós precisamos do valor do indicador média móvel, correspondente à penúltima barra (completada por último), e o valor de nosso indicador, correspondente à última barra, portanto, estamos copiando estes dois elementos para o array ma[] e um elemento para o array atr_h[] e atr_I[]. No caso de erro ao copiar ou se o número de valores copiados é menor que o necessário (para qualquer um destes arrays), a mensagem é impressa no registro dos Experts e a função OnTick é terminada utilizando o operador return.

Para o array ma[], nós definimos a sinalização AS_SERIES, que indica a indexação timeseries do array.

   ArraySetAsSeries(ma,true);

Os arrays atr_[] e atr_I[] possuem apenas um elemento, portanto a indexação timeseries não é importante. Como o valor atr_l[0] será utilizado adiante para determinar o nível de TakeProfit (Lucro máximo), posições curtas são fechadas a preço de Compra (Ask), mas nós adicionamos o spread ao valor de atr_l[0], pois os preços de Venda (Bid) são utilizados nos gráficos de preço.

   atr_l[0]+=Spread;

A função PositionsTotal retorna o número de posições abertas (ela não possui parâmetros). Os índices de posições começam do 0. Vamos criar um loop (repetição), que buscará todas as posições em aberto:

// neste loop verificaremos todas as posições em aberto
   for(i=0;i<PositionsTotal();i++)
     {
      // processando apenas as ordens com o "nosso" símbolo
      if(Symbol()==PositionGetSymbol(i))
        {
         // mudaremos os valores do StopLoss e TakeProfit
         request.action=TRADE_ACTION_SLTP;
         // processando posições compradas
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
           {
            // vamos determinar o StopLoss
            if(ma[1]>PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]; else StopLoss=lev_l;
            // se o StopLoss não foi definido ou for menor que o necessário            
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(StopLoss-PositionGetDouble(POSITION_SL),_Digits)>0
               // se o TakeProfit não foi definido ou for maior que o necessário
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(PositionGetDouble(POSITION_TP)-atr_h[0],_Digits)>0)
               // o novo StopLoss está próximo do preço atual?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLoss-StopLevel,_Digits)>0
               //  o novo TakeProfit está próximo do preço atual?
               && NormalizeDouble(atr_h[0]-SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLevel,_Digits)>0)
              {
               // colocando novos valores de StopLoss à estrutura
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // colocando novos valores de TakeProfit à estrutura
               request.tp=NormalizeDouble(atr_h[0],_Digits);
               // Enviando a solicitação ao servidor de negócio
               OrderSend(request,result);
              }
           }
         // processando posições vendidas
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            // vamos determinar o valor do StopLoss
            if(ma[1]+Spread<PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]+Spread; else StopLoss=lev_h;
            // se o StopLoss não foi definido ou for maior que o necessário
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(PositionGetDouble(POSITION_SL)-StopLoss,_Digits)>0
               // se o TakeProfit não foi definido ou for menor que o necessário
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(atr_l[0]-PositionGetDouble(POSITION_TP),_Digits)>0)
               // o novo StopLoss está próximo do preço atual?
               && NormalizeDouble(StopLoss-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0
               // o novo TakeProfit está próximo do preço atual??
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK)-atr_l[0]-StopLevel,_Digits)>0)
              {
               // colocando novos valores de StopLoss à estrutura
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // colocando novos valores de TakeProfit à estrutura
               request.tp=NormalizeDouble(atr_l[0],_Digits);
               // Enviando a solicitação ao servidor de negócio
               OrderSend(request,result);
              }
           }
         // Se houver uma posição em aberto, retorne daqui...
         return;
        }
     }
Utilizando o operador if dentro do loop, nós escolhemos as posições que foram abertas para o símbolo do gráfico atual. A função PositionGetSymbol retorna o símbolo do instrumento, além disso, ela seleciona automaticamente a posição com a qual trabalhar. A função possui apenas um parâmetro - o índice de posição na lista de posições abertas. É possível que seja necessário alterar os valores de StopLoss e TakeProfit das posições em aberto, então vamos colocar o valor TRADE ACTION SLTP no elemento request.action. A seguir, dependendo de sua direção, as posições são divididas em compradas e vendidas.

Para posições comprada, o nível de StopLoss é determinado a seguir: Se o valor do indicador média móvel for maior que o preço da posição em aberto, presume-se que o valor de StopLoss seja igual ao valor do indicador média móvel, portanto, presume-se que o valor de StopLoss seja igual ao valor da variável lev_I. O valor atual de StopLoss para a posição em aberto é determinado utilizando a função PositionGetDouble, que possui apenas um parâmetro - o identificador da propriedade da posição. Se o valor de StopLoss não está definido para posição em aberto (igual a 0), ou for maior do que deveria ser - nós modificaremos os valores de StopLoss e TakeProfit para esta posição. Se o valor de TakeProfit não está definido (igual a 0), ou for maior do que deveria ser - (maior que a linha superior de nosso indicador) - nós modificaremos os valores de StopLoss e TakeProfit para esta posição.

Nós temos que verificar a possibilidade de alterar os valores de StopLoss e TakeProfit. O novo valor de StopLoss deve ser menos que o preço atual de venda (Bid) (pelo menos para o valor de STOP_LEVEL), o novo valor de TakeProfit deve ser maior que o preço atual de Venda (Bid) pelo menos para o valor de STOP_LEVEL. Nós utilizamos a diferença normalizada para comparar, pois, os valores comparados podem diferenciar-se nos últimos dígitos, por causa da imprecisão causada pela conversão de números binários de ponto flutuante do tipo double, em números decimais de ponto flutuante.

Se for necessário alterar os níveis de StopLoss ou TakeProfit para a posição em aberto, e seus novos valores são válidos de acordo com regras de negócio, nós colocamos os novos valores de StopLoss e TakeProfit nos elementos correspondentes a estrutura e solicitamos a função OrderSend para enviar os dados ao servidor de negócio.

A alteração dos valores de StopLoss e TakeProfit para as posições vendidas são as mesmas. As posições vendidas, comparadas com as compradas, são fechadas pelos preços de Compra, portanto os valores de Compra serão utilizados para comparação. Se houver uma posição em aberto para o gráfico atual - nó finalizamos a execução da função OnTick utilizando o operador return.

A função OrdersTotal (não possui parâmetros) retorna o número de ordens pendentes. Os índices começam do 0. Vamos criar um loop ( repetição) ,que será utilizado para processar todas as ordens pendentes:

// neste loop verificaremos todas as ordens pendentes
   for(i=0;i<OrdersTotal();i++)
     {
      // escolhendo cada ordem e obtendo seu ingresso
      ticket=OrderGetTicket(i);
      // processando apenas as ordens com o "nosso" símbolo
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         // processando ordens de Buy Stop 
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP)
           {
            // verificando se estamos no horário de negociações e se é possível haver movimento de preços
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_h<atr_h[0])
              {
               // se o preço de abertura é menor do que o necessário
               if((NormalizeDouble(lev_h-OrderGetDouble(ORDER_PRICE_OPEN),_Digits)>0
                  // se o StopLoss não foi definido ou for maior que o necessário
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(OrderGetDouble(ORDER_SL)-lev_l,_Digits)!=0)
                  // o preço de abertura está próximo do preço atual?
                  && NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0)
                 {
                  // alternado os parâmetros da posição pendente
                  request.action=TRADE_ACTION_MODIFY;
                  // colocando o número de ingresso para a estrutura
                  request.order=ticket;
                  // colocando o novo valor do preço de abertura à estrutura
                  request.price=NormalizeDouble(lev_h,_Digits);
                  // colocando o novo valor de StopLoss à estrutura
                  request.sl=NormalizeDouble(lev_l,_Digits);
                  // enviando a solicitação ao servidor de negócio
                  OrderSend(request,result);
                  // saindo da função OnTick()
                  return;
                 }
              }
            // se estivermos fora da hora de negociação ou se não estamos no intervalo médio para negociações
            else
              {
               // removeremos todas as ordens pendetes
               request.action=TRADE_ACTION_REMOVE;
               // colocando o número de ingresso para a estrutura
               request.order=ticket;
               // enviando a solicitação ao servidor de negócio
               OrderSend(request,result);
               // saindo da função OnTick()
               return;
              }
            // definindo o sinalizador, que indica se há ordens de Buy Stop
            bord=true;
           }
         // processando as ordens de Sell Stop
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
           {
            // verificando se estamos no horário de negociações e se é possível haver movimento de preços
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_l>atr_l[0])
              {
               // se o preço de abertura é maior do que o necessário
               if((NormalizeDouble(OrderGetDouble(ORDER_PRICE_OPEN)-lev_l,_Digits)>0
                  // se o StopLoss não foi definido ou for menor que o necessário
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(lev_h-OrderGetDouble(ORDER_SL),_Digits)>0)
                  // o preço de abertura está próximo do preço atual?
                  && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>0)
                 {
                  // alternado os parâmetros da posição pendente
                  request.action=TRADE_ACTION_MODIFY;
                  // colocando o número de ingresso para a estrutura
                  request.order=ticket;
                  // colocando o novo valor do preço de abertura à estrutura
                  request.price=NormalizeDouble(lev_l,_Digits);
                  // colocando o novo valor de StopLoss à estrutura
                  request.sl=NormalizeDouble(lev_h,_Digits);
                  // enviando a solicitação ao servidor de negócio
                  OrderSend(request,result);
                  // saindo da função OnTick()
                  return;
                 }
              }
            // se estivermos fora da hora de negociação ou se não estamos no intervalo médio para negociações
            else
              {
               // removeremos todas as ordens pendetes
               request.action=TRADE_ACTION_REMOVE;
               // colocando o número de ingresso para a estrutura
               request.order=ticket;
               // enviando a solicitação ao servidor de negócio
               OrderSend(request,result);
               // saindo da função OnTick()
               return;
              }
            // definindo o sinalizador, que indica se há ordens de Sell Stop
            sord=true;
           }
        }
     }
Utilizando a função OrderGetTicket, nós selecionamos a ordem para com a qual trabalhar adiante, e nós salvamos o ingresso da ordem na variável ticket. Esta função possui apenas um parâmetro - o índice da ordem na lista de ordens em aberto. A função OrderGetString é utilizada para obter o nome do símbolo. Ela possui apenas um parâmetro - o identificador de propriedade da ordem. Nós estamos comparando o nome do símbolo com o nome do gráfico atual, permitindo selecionar ordens apenas pelo instrumento, no qual o Expert Advisor está sendo executado. O tipo de ordem é determinado pela função OrderGetInteger com o correspondente identificador de tipo de ordem. Nós processaremos separadamente as ordens de Buy Stop e Sell Stop.

Se a hora atual está na faixa de StartHour a EndHour e o preço de abertura da ordem Buy Stop não exceder a linha superior do indicador, modificaremos o preço de abertura e o valor do nível StopLoss (se necessário), caso contrário, nós excluímos a ordem.

A seguir, nós determinaremos , será que é necessário modificar o preço de abertura do nível de StopLoss para a ordem pendente? Se o preço de abertura da ordem Buy Stop for menor do que deveria ser, ou se seu StopLoss não está definido ou for maior, nós colocaremos o valor TRADE ACTION MODIFY ao elemento request.action - isso significa que os parâmetros da ordem pendente devem ser alterados. Colocaremos também o ingresso da ordem ao elemento request.ticket e enviaremos uma solicitação de negócio ao servidor de negócio utilizando a função OrderSend. Em comparação, nós determinamos o caso quando o preço de abertura é menor do que deveria ser, pois o valor do spread pode ser variado, mas nós não modificamos a ordem após cada mudança de spread, ela será definida no nível mais alto, que corresponde ao valor máximo de spread.

Também em comparação, vamos determinar, se for o caso, quando o valor de StopLoss for mais alto do que deveria ser, pois a faixa de preço pode expandir durante o dia, e é necessário diminuir o valor da ordem de StopLoss após as novas quedas de preço. Após enviar a solicitação ao servidor de negócio, a execução da função OnTick é finalizada utilizando o operador return. Se houver ordens de Buy Stop, o valor da variável bord é definido para true.

As ordens de Sell Stop são processadas da mesma forma que as ordens de Buy Stop..

Agora vamos colocar ordens pendentes de Buy Stop e Sell Stop no caso de suas ausências. Colocaremos o valor de TRADE ACTION PENDING no elemento request.action (isso significa que a ordem pendente é colocada).

   request.action=TRADE_ACTION_PENDING;

Se o valor da hora atual estiver dentro do horário de estabelecimento de pedido, nós estabelecemos as ordens:

   if(dt.hour>=StartHour && dt.hour<EndHour)
     {
      if(bord==false && lev_h<atr_h[0])
        {
         request.price=NormalizeDouble(lev_h,_Digits);
         request.sl=NormalizeDouble(lev_l,_Digits);
         request.type=ORDER_TYPE_BUY_STOP;
         OrderSend(request,result);
        }
      if(sord==false && lev_l>atr_l[0])
        {
         request.price=NormalizeDouble(lev_l,_Digits);
         request.sl=NormalizeDouble(lev_h,_Digits);
         request.type=ORDER_TYPE_SELL_STOP;
         OrderSend(request,result);
        }
     }
  }
Ao colocar ordens de Buy Stop e Sell Stop, nós estamos verificando pela presença dos mesmas ordens, analisando os valores das variáveis bord e sord. Nós também verificaremos a seguinte condição: o preço da ordem deve estar dentro dos valores de nosso indicador. O preço de ordem normalizado é colocado no elemento request.price, o valor normalizado de StopLoss é colocado na variável request.sl, o tipo de ordem (ORDER_BUY_STOP ou ORDER_SELL_STOP) é colocado na variável request.type. Após isso, nós enviamos a solicitação ao servidor de negócio. O código da função OnTick termina com um ponto e vírgula.

Os recursos, alocados pelos indicadores, são liberados dentro da função OnDeinit utilizando a função IndicatorRelease, que foi considerada acima.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hCI);
   IndicatorRelease(hMA);
  }

O Expert Advisor está completo, a compilação deve ser bem sucedida se não houver nenhum erro. Agora nós podemos executá-lo, anexando-o ao gráfico. O código-fonte pode ser baixado dos anexos deste artigo.


Executando e Depurando

Quando o Expert Advisor e o Indicator estiverem prontos, vamos considerar como executá-los e como depurá-los utilizando o depurador incorporado do MetaEditor.

Para executar um Expert Advisor, é necessário encontrá-lo no grupo dos Expert Advisors na janela Navegador. Feito isso, escolha Anexar ao gráfico no menu de contexto, que aparecerá ao clicar com o botão direito do mouse:

Figura 11. Executando um Expert Advisor.

A janela com parâmetros de entrada do Expert Advisor aparecerá, você pode alterar estes parâmetros se for necessário. Após pressionar OK, o ícone aparecerá no canto superior direito da tabela, este ícone indica que o Exp ert Advisor está funcionando. Para exibir ou fechar a janela do Navegador, você pode escolher Navegador no menu Exibir ou pressionar Ctrl+N. A segunda maneira de executar o Expert Advisor é selecioná-lo no sub-menu Experts no menu Inserir

Para que o Expert Advisor seja capaz de negociar, a Negociação Automatizada deve ser permitida nas opções do Terminal Client: Menu Ferramentas -> janela Opções -> aba Expert Advisors -> a opção Permitir Negociação Automatizada deve estar habilitada. Para que o Expert Advisor seja capaz de solicitar funções a partir de DLLs, a opção Permitir importação de DLL deve estar habilitada.

Figura 12. Opções do Terminal - Permitir Negociação Automatizada.

Além disso, você pode definir a autorização ou proibição de trading e importação de bibliotecas externas de DLL para cada Expert Advisor individualmente, verificando suas opções correspondentes.

Apesar do fato de que nosso Expert Advisor utiliza indicadores, as linhas dos indicadores não são plotadas no gráfico. Se necessário, você pode anexar os indicadores manualmente.

Executar Indicadores é o mesmo para Expert Advisors: se você deseja executar indicadores incorporados, expanda a árvore Indicadores na janela Navegador (para os indicadores personalizados, é necessário expandir a árvore Indicadores personalizados), dê um clique com o botão direito para o menu pop-up aparecer, então escolha Anexar ao gráfico do menu de contexto. A segunda maneira é escolher Indicadores no menu Inserir, escolher o grupo (ou escolha Personalizados para indicadores personalizados) e o próprio indicador.

Os scripts são executados da mesma maneira que os Expert Advisors e os Indicadores.

As informações sobre os eventos do Terminal Client (conexão/desconexão para/do servidor de trade, atualização automática, alterações de posições e ordens, Expert Advisors e Scripts funcionando, mensagens de erro) podem ser encontradas na aba Diário da janela Caixa de Ferramentas. As mensagens que foram impressas pelos Expert Advisors, Indicadores e Scripts estão localizadas na aba Experts.

O MetaEditor possui um depurador incorporado. Ele permite que você depure programas - execução passo a passo dos Expert Advisors, Indicadores e Scripts. Depurar ajuda a encontrar erros no código do programa e observar os processos durante a execução dos Expert Advisors, Indicadores ou Scripts. Para executar um programa em modo de depuração, é necessário escolher Iniciar, no menu Depurar ou pressionar a tecla F5. O programa será compilado e funcionará em modo de depuração em um gráfico separado, seu período e símbolo podem ser especificados na aba Depuração da janela Opções do MetaEditor.

Figura 13. Opções do Editor - Depuração.

Você pode definir os pontos de interrupção pressionando a tecla F9, ou clicando duas vezes no lado esquerdo da linha, ou escolhendo Alternar Ponto de Quebra na janela Depurar. Em modo de depuração, a execução do programa irá parar antes do operador com ponto de interrupção. Após a parada, a aba Depurar irá aparecer na janela Caixa de Ferramentas (Figura 14.). No lado esquerdo, há um painel empilhador de solicitações - arquivo, função e número de uma linha são exibidos lá. No lado direito, há um painel de observação - valores de variáveis observadas são exibidos lá. Para adicionar a variável à lista de observação, clique com o botão direito sobre o painel e selecione Adicionar ou pressione a tecla Insert.

Figura 14. Depuração do programa.

A execução passo a passo do programa pode ser realizada pressionando as teclas F11, F10 ou Shift+F11. Após pressionar a tecla F11 ou escolher Entrar no menu Depurar, ele passará uma etapa da execução do programa inserindo todas as funções solicitadas. Após pressionar a tecla F10 ou escolher Passar por cima no menu Depurar, ele passará uma etapa da execução do programa sem entrar nas funções solicitadas. Após pressionar a tecla Shift+F11 ou escolher Sair no menu Depurar, ele fará a execução do progrmaa a um passo, em um nível mais alto. A seta verde no lado esquerdo do código marca a linha do código, que será executada.


Conclusão

No artigo, foi apresentado um exemplo de como escrever um simples Expert Advisor e um Indicator, foram descritos os conceitos básicos da linguagem de programação MQL5. O sistema de negócios, fornecido aqui foi escolhido como exemplo, portanto, o autor não é responsável por seu uso em uma negociação real.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/35

Arquivos anexados |
expert.mq5 (23.58 KB)
indicator_tp.mq5 (4.43 KB)
Usando os Ponteiros de Objeto no MQL5 Usando os Ponteiros de Objeto no MQL5
Predefinidamente, todos os objetos no MQL5 são passados por referência, mas há a possibilidade de usar os ponteiros de objeto. Porém, é necessário realizar a verificação do ponteiro, porque o objeto pode não ser inicializado. Neste caso, o programa MQL5 será finalizado com o erro crítico e descarregado. Os objetos, criados automaticamente, não causam tal erro, então, neste sentido, são bastante seguros. Neste artigo, tentaremos entender a diferença entre a referência do objeto e o ponteiro do objeto, e considere como escrever o código seguro, que usa os ponteiros.
MQL5 para Novatos: Guia para o Uso de Indicadores Técnicos em Expert Advisors MQL5 para Novatos: Guia para o Uso de Indicadores Técnicos em Expert Advisors
Para obter valores de um indicador interno ou personalizado em um Expert Advisor, primeiro seu manipulador deve ser criado usando a função correspondente. Exemplos no artigo mostram como usar este ou aquele indicador técnico durante a criação de seus próprios programas. O artigo descreve os indicadores que são construídos utilizando a linguagem MQL5. Ele é destinado para aqueles que não têm muita experiência no desenvolvimento de estratégias de negociação, oferecendo maneiras simples e claras de trabalhar com indicadores utilizando a biblioteca de funções oferecida.
Indicadores personalizados no MQL5 para novatos Indicadores personalizados no MQL5 para novatos
Qualquer assunto novo parece complicado e difícil de aprender para um principiante. Os assuntos que conhecemos parecem muito simples e claros para nós. Mas, simplesmente não lembramos, que todos têm que estudar algo desde o início, até a nossa língua materna. O mesmo é com a linguagem de programação MQL5 que oferece amplas possibilidades para desenvolver estratégias próprias de negociação - você pode aprender a partir de noções básicas e dos exemplos mais simples. A interação de um indicador técnico com o terminal de cliente MetaTrader 5 é considerada neste artigo sobre o exemplo de um indicador personalizado simples SMA.
Mergulhe rapidamente na linguagem MQL5 Mergulhe rapidamente na linguagem MQL5
Você decidiu estudar a linguagem de programação das estratégias de negócio do MQL5, mas não sabe nada sobre isso? Tentamos examinar o MQL5 e o terminal MetaTrader 5 a partir do ponto de vista de iniciantes e escrevemos este pequeno artigo introdutório. Neste artigo, você pode ter uma breve ideia das possibilidades da linguagem, assim como dicas sobre como trabalhar com o MetaEditor 5 e o terminal.