
Aprendendo MQL5 do iniciante ao profissional (Parte V): Principais operadores de redirecionamento do fluxo de comandos
Introdução
Neste artigo da série, falaremos sobre como ensinar o programa a tomar decisões. Para esses fins, criaremos um indicador. Vamos fazer um indicador que exiba barras internas ou externas.
- Uma barra externa será aquela que rompe a faixa da vela anterior em ambas as direções.
- Uma barra interna será aquela que, após o fechamento, permanece dentro da faixa da vela anterior.
- Marcaremos cada vela fechada. Se a vela ainda estiver mudando, não colocaremos marcas nela.
- Os marcadores serão colocados apenas em "velas longas", ou seja, para a "barra externa" — na barra atual, e para a "barra interna" — na anterior. Essa decisão pode ser discutível, portanto, se você achar o indicador útil, mas não concordar com essa escolha, fique à vontade para alterar os índices correspondentes no código apropriado.
- Faremos nosso indicador universal. Ele poderá exibir ambas as variantes, e a escolha será feita pelo usuário por meio dos parâmetros de entrada.
Durante a criação deste indicador, usaremos os operadores abordados no artigo. Como o indicador é simples, serão usados apenas operadores de laço e condição, e isso é tudo o que precisamos neste caso. No entanto, por questão de prática, podemos tentar reescrevê-lo:
- usando outros tipos de laços;
- usando o operador switch-case em vez de algumas (ou todas) as instruções condicionais.
Até agora, nossos programas não eram muito úteis, pois não permitiam escolha e precisavam executar nossas instruções linearmente, passo a passo. Após a leitura deste artigo, seus programas poderão se tornar muito mais completos.
Iniciaremos com uma análise detalhada das expressões booleanas (ou seja, lógicas), pois elas são a base para a compreensão de todo o restante do material deste artigo.
Expressões lógicas em detalhes
Lembre-se de que os dados do tipo booleano (bool) têm apenas dois valores: ou true (verdadeiro) ou false (falso).
Podemos dizer que, em cada caso de uso de operadores lógicos, o programa faz uma pergunta aos seus dados atuais: "É verdade que (condição)?". ("É verdade que agora (prev_calculated == rates_total)? Se sim, então é hora de eu encerrar a execução da função atual! Caso contrário, precisarei calcular algo novamente...").
No MQL5, o valor false é representado pelo número 0. Consequentemente, todos os outros números em expressões lógicas serão convertidos para true, independentemente de serem positivos ou negativos.
As strings não podem ser convertidas para o tipo bool. Se tentarmos tal conversão, o compilador retornará um erro. No entanto, é possível compará-las.
Na maioria das vezes, os dados lógicos são obtidos a partir de expressões lógicas. A operação de comparação é o caso mais simples desse tipo de expressão. No MQL5, são utilizados os mesmos operadores de comparação da matemática escolar: maior (>), menor (<), igual (==), diferente (!=), maior ou igual (>=) e menor ou igual (<=). Assim, a expressão (a == b) será true somente quando ambos os elementos forem iguais entre si. Creio que isso está claro.
Ao comparar strings, a maior será aquela que tiver caracteres com valores mais altos na tabela de caracteres logo no início. Por exemplo, ("A" > "1") é verdadeiro, enquanto ("Y" < "A") é falso. Se a string contiver vários caracteres, esse princípio se mantém. Primeiro, os primeiros caracteres são comparados; se forem iguais, os segundos são comparados, e assim por diante, até que haja uma diferença. Além disso, uma string vazia ("") é menor que todas as outras. Ou seja, se uma string tiver menos caracteres, mas os caracteres iniciais forem idênticos, ela será considerada menor na comparação.
Existem mais três operações lógicas:
- Negação lógica (também chamada de operação "não" ou inversão), representada pelo ponto de exclamação (!);
Transforma um valor lógico em seu oposto. - Multiplicação lógica (também chamada de operação "e" ou conjunção), representada por dois símbolos de ampersand consecutivos (&&);
Uma expressão com esse operador será verdadeira somente se ambas as partes da expressão forem verdadeiras (tanto a esquerda quanto a direita). - Soma lógica (também chamada de operação "ou" ou disjunção), representada por dois símbolos de barra vertical consecutivos (||);
Uma expressão com esse operador será verdadeira se pelo menos uma de suas partes for verdadeira.
Tabela 1. Negação lógica (!) | Tabela 2. Multiplicação lógica (&&) | Tabela 3. Adição lógica (||) |
![]() | ![]() | ![]() |
Para fixar melhor o conteúdo, tente determinar por conta própria o resultado de cada uma das expressões abaixo. Nos comentários, há dicas, mas recomendo que você tente resolver sem olhar e use as dicas apenas para conferir suas respostas.
int a = 3; int b = 5; bool e = ( a*b ) > ( a+b+b ); // true Print ( (a>b) ); // false Print ( (a<b) || ((a/b) > (3/2)) ); // true Print ( !a ); // false Print ( ! (!e) ); // true Print ( (b>a) && ((a<=3) || (b<=3)) ); // true Print ( "Trust" > "Training" ); // true Print ( a==b ); // false Print ( b=a ); // 3 (!!!) (therefore, for any logical operator this is always true) // In the last example, a very common and hard-to-detect mistake has been made. // Instead of a comparison, an assignment was used, which results in a value of type int! // Fortunately, the compiler will usually warn about this substitution.
Exemplo 1. Exemplos de uso de operadores lógicos
Se uma expressão contiver vários operadores sem parênteses, eles geralmente serão executados da esquerda para a direita (exceto a atribuição), e a ordem de execução será a seguinte:
- Primeiro, todos os operadores aritméticos serão executados:
- *, /, %
- +, -
- Em seguida, serão executados os operadores de comparação: (==, !=, >, <, >=, <=).
- Depois, os operadores lógicos serão processados na seguinte ordem:
- Inversão (!)
- Conjunção (&&)
- Disjunção (||)
- O operador de atribuição (=) será executado por último.
Se você não tem certeza sobre a ordem de execução do seu código, use parênteses, como no meu exemplo 1. Eles sempre ajudam, pois as operações dentro deles são executadas primeiro. Além disso, a indentação deixa o código mais visualmente claro.
Operador if
O operador if também é chamado de operador condicional, de ramificação ou de decisão. Traduzindo do inglês, "if" significa "se", e "else" significa "senão".
A sua estrutura é bem simples:
if (condition) action_if_condition_is_true; else action_in_all_other_cases; // The "else" branch is optional
Exemplo 2. Esquema de operador condicional.
Por que "ramificação"? Se representarmos esse operador graficamente, obteremos algo assim:
Figura 1. Operador de ramificação
Se você tem uma boa imaginação, pode visualizar como, a partir do "tronco" principal, esse operador faz crescer um "galho" adicional (uma ramificação). Nos diagramas mais antigos (em termos de época em que foram concebidos), os algoritmos às vezes pareciam verdadeiros "arbustos". Por exemplo, o que fazer se você quiser ler um livro, mas a luminária da mesa não acende? Primeiro, tentar ligá-la — pode ser que funcione. Se não acender, verificar se a lâmpada está no soquete — talvez alguém tenha removido por necessidade. Se a lâmpada estiver lá e não ligar, verificar se queimou. Se estiver queimada, tentar trocar. Se nada disso funcionar, tentar outra lâmpada. Esse algoritmo pode ser representado visualmente em um diagrama como este:
Figura 2. Ilustração de ramificações
Aqui, para ilustrar o conceito de "ramificação", o algoritmo é executado de baixo para cima, a partir do ponto verde "Start". Acredito que, neste diagrama, fica bastante claro como a ramificação ocorre em cada ponto de decisão. Assim como na ilustração anterior, os pontos de decisão estão marcados em azul.
Vale lembrar que, no MQL5, tanto "ação_se_condição_é_verdadeira" quanto "ação_nos_outros_casos" podem ser apenas um operador, mas, se necessário, esse operador pode ser composto.
Explicarei melhor. No MQL5, as ações podem (ou não) ser colocadas entre chaves {}. Elas funcionam como uma unidade separada e delimitam um bloco de código específico. Variáveis declaradas dentro desse bloco não serão visíveis para outros blocos. Em alguns casos, o uso das chaves é obrigatório pela própria sintaxe da linguagem, mas em muitos outros, elas podem ser inseridas no código de maneira quase arbitrária. O conteúdo dentro dessas chaves será interpretado pelo restante do programa como uma única entidade. Por exemplo, variáveis descritas dentro de um bloco não estarão acessíveis fora dele. Esse bloco de código delimitado por chaves é o que chamamos de comando composto. Dentro dele, podemos ter quantas ações forem necessárias, mas comandos como if continuarão a ser executados apenas até encontrarem a chave de fechamento do bloco principal. No entanto, se não houver chaves, somente o primeiro comando será executado quando a condição for verdadeira, enquanto os demais seguirão o fluxo normal do programa.
Com frequência, referimo-nos aos comandos subordinados a um operador (como if ou else) como o corpo do operador.
No esquema do exemplo 2, o corpo do operador if será "ação_se_condição_for_verdadeira", enquanto o corpo do operador else será "ação_em_todos_os_outros_casos".
Aqui está um exemplo prático. Este código verifica uma nova vela no indicador, cujo modelo foi apresentado no exemplo 16. O algoritmo baseia-se na comparação entre o número de velas já calculadas (prev_calculated) e o total de velas no gráfico (rates_total):
if (prev_calculated == rates_total) // _If_ they are equal, { // _then_ do nothing, wait for a new candlestick Comment("Nothing to do"); // Display a relevant message in a comment return(rates_total); // Since nothing needs to be done, exit the function // and inform the terminal that all bars have been calculated (return rates_total) } // A new candle has arrived. Execute necessary actions Comment(""); // Clear comments Print("I can start to do anything"); // Log a message // Since the required actions will be executed // only if prev_calculated is not equal to rates_total, // else branch is not needed in this example. // We just perform the actions.
Exemplo 3. Exemplo de espera por uma nova vela comparando prev_calculated e rates_total
Neste trecho, o corpo do operador if contém dois comandos: Comment e return. O operador else foi omitido.
Se testarmos esse exemplo, veremos o mesmo princípio descrito anteriormente. Dentro das chaves, podemos usar quantos operadores forem necessários. No entanto, se removermos essas chaves, a condição executará apenas a chamada da função Comment("Nothing to do"). Nesse caso, o operador return será executado incondicionalmente, e a mensagem de prontidão para operação nunca será exibida (recomendo testar isso em um gráfico de um minuto).
Minha recomendação sobre a codificação do corpo dos operadores é a seguinte:
Sempre utilize chaves para delimitar o corpo do operador if e de outros operadores, mesmo que esse corpo contenha apenas um único comando.
Siga essa recomendação até adquirir experiência suficiente, por exemplo, analisando o código de outros programadores na CodeBase. O uso de chaves torna a depuração de programas mais intuitiva e evita que você passe horas tentando corrigir erros aparentemente óbvios (vale lembrar que esses erros só serão "óbvios" para programadores experientes). Além disso, ao utilizar chaves mesmo para um único comando, será mais fácil adicionar novas ações posteriormente, se necessário. Exemplos comuns dessas "adições" incluem mensagens de depuração ou notificações ao usuário, mas o uso não se limita apenas a isso.
Operador ternário
Às vezes, usamos operadores de seleção para atribuir a uma variável um de dois valores possíveis. Por exemplo:
int a = 9; int b = 45; string message; if( (b%a) == 0 ) // (1) { message = "b divides by a without remainder"; // (2) } else { message = "b is NOT divisible by a"; // (3) } Print (message);
Exemplo 4. Exemplo de uma condição adequada para conversão em um operador ternário
Neste caso, podemos simplificar um pouco a escrita usando o operador ternário (composto por três partes):
int a = 9; int b = 45; string message; message = ( (b%a) == 0 ) /* (1) */ ? "b divides by a without remainder" /* (2) */ : "b is NOT divisible by a" /* (3) */ ; Print (message);
Exemplo 5. Operador ternário
A estrutura aqui é semelhante à do operador if: condição -> (ponto de interrogação) -> valor_se_condição_verdadeira -> (dois pontos, em vez de else) -> valor_se_condição_falsa. É claro que nada impede o uso de operadores ternários aninhados, caso seja adequado para a tarefa em questão.
A principal diferença desse operador em relação ao if tradicional é que o operador ternário deve retornar um valor compatível com o tipo da variável à esquerda do sinal de atribuição. Por isso, ele só pode ser usado em expressões, ao passo que o if tradicional contém expressões dentro de seu corpo, mas não pode retornar um valor diretamente.
Operadores switch-case
Às vezes, precisamos escolher entre mais de duas opções. Embora pudéssemos usar múltiplos operadores if aninhados, isso tornaria o código menos legível. Para esses casos, existe o operador switch (interruptor). Ele segue o seguinte modelo:
switch (integer_variable) { case value_1: operation_list_1; case value_2 operation_list_2; // … default: default_operation_list; }
Exemplo 6. Estrutura do operador switch-case
Ele funciona assim: se o valor da variável inteira corresponder a um dos valores (valor_1, valor_2, etc.), a lista de operações associada a esse valor será executada, seguida por todas as listas subsequentes. No entanto, na maioria das vezes, não queremos que todos os blocos seguintes sejam executados automaticamente. Por isso, costumamos adicionar um operador break após cada bloco de operações, para encerrar o switch assim que um caso for resolvido. Se nenhum valor corresponder, a seção padrão será executada. Essa seção deve estar sempre no final da estrutura.
No MQL5, o operador switch-case é amplamente utilizado para o tratamento de erros em Expert Advisors (EA) e para a manipulação de eventos, como o pressionamento de teclas no teclado ou movimentos do mouse. A seguir, um exemplo de trecho típico de código de uma função de tratamento de erros:
void PrintErrorDescription() { int lastError = GetLastError(); // If (lastError == 0), there are no errors… if(lastError == 0) { return; // …no need to load cpu with unnecessary computations. } // If there are errors, output an explanatory message to the log. switch(lastError) { case ERR_INTERNAL_ERROR: Print("Unexpected internal error"); // You can select any appropriate action here break; case ERR_WRONG_INTERNAL_PARAMETER: Print("Wrong parameter in the inner call of the client terminal function"); break; case ERR_INVALID_PARAMETER: Print("Wrong parameter when calling the system function"); break; case ERR_NOT_ENOUGH_MEMORY: Print("Not enough memory to perform the system function"); break; default: Print("I don't know anything about this error"); } }
Exemplo 7. Um dos esquemas típicos de tratamento de erros
Operador de laço com condição prévia (while)
Um laço é um operador que executa ações repetitivas.
É muito comum (praticamente sempre) em programação encontrar situações que exigem a repetição de certas ações, mas com dados que mudam ligeiramente a cada iteração. Por exemplo, podemos percorrer todos os elementos de um array, como o buffer de um indicador, para visualizar seu comportamento no histórico. Da mesma forma, podemos ler os parâmetros de um EA complexo a partir de um arquivo, linha por linha. E assim por diante.
No MQL5, existem três tipos de laços, consoante a necessidade da tarefa. Todos eles são intercambiáveis e, se necessário, um pode sempre ser substituído por outro sem que haja perda de funcionalidade. No entanto, cada um tem um caso de utilização em que é mais conveniente.
O primeiro operador de laço é o while (enquanto). Ele é frequentemente chamado de laço com condição prévia, porque a verificação da condição ocorre antes da execução das ações dentro do laço. Graficamente, ele pode ser representado assim:
Figura 3. Esquema do laço while
O modelo de código desse operador é muito simples:
while (condition)
action_if_condition_is_true;
Exemplo 8. Esquema do laço while
Aqui, "ação_se_condição_for_verdadeira" pode ser um operador único ou um bloco de operadores compostos.
Se o programa encontrar um operador while, ele primeiro verifica a condição. Se a condição for verdadeira, as ações dentro do laço são executadas e a condição é verificada novamente. Isso se repete até que a condição se torne falsa. Como a condição é verificada antes da execução do corpo do laço, pode haver situações em que o laço não seja executado nenhuma vez.
Normalmente, esse operador é usado em casos nos quais não é possível determinar previamente o número exato de repetições. Ao ler um arquivo de texto, por exemplo, o programa não sabe quantas linhas há no arquivo e precisa se basear em um indicador especial de fim de arquivo. Enquanto esse indicador não for encontrado, devemos continuar lendo e processando as linhas. Assim que o indicador for detectado, a leitura deve ser interrompida. Esse tipo de situação é bastante comum na programação.
Em qualquer caso, pelo menos um parâmetro deve mudar dentro do corpo do laço, influenciando a condição dentro dos parênteses. Se não houver uma variável que altere o valor da condição, o laço se tornará infinito e só poderá ser interrompido fechando a janela do terminal. Quando um laço infinito está em execução, encerrar o programa pode ser difícil, pois o processador pode estar ocupado executando esse código, sem tempo para responder a ações como o pressionamento de teclas ou movimentos do mouse. No pior cenário, pode ser necessário reiniciar o computador pelo botão de hardware. Portanto, sempre revise seus laços cuidadosamente e garanta que há pelo menos uma maneira de interrompê-los. Uma boa prática é adicionar uma condição extra à verificação, como (&& ! IsStopped()), que encerrará o laço caso o programa seja fechado.
Para comparar a forma de escrita dos diferentes laços, tomemos como exemplo a mesma tarefa de somar os números de 1 a 5. Suponha que não sabemos sobre progressões matemáticas.
Usando o laço while, essa tarefa pode ser resolvida da seguinte forma:
//--- Variables declaration int a = 1; // initialize (!) the variable parameter used in the condition int sum = 0; // result string message = "The sum of "+ (string) a; // show message //--- Perform main operations while (a <= 5) // While a is less than 5 { sum += a; // Add the value to the sum if ( a != 1) // The first value is already added at the time of initialization { message += " + " + string (a); // Further operations } a++; // Increase parameter (very important!) } //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Exemplo 9. Usando o laço while
Aqui, obviamente, o número de iterações é fácil de calcular. No entanto, o objetivo do exemplo é demonstrar a forma de funcionamento do laço, e para isso ele serve bem.
Laço com pós-condição (do… while)
O laço do… while (execute… enquanto) verifica a condição depois de executar todas as ações dentro do seu corpo. Graficamente, esse laço pode ser representado assim:
Figura 4. Esquema do laço do-while
O modelo de código desse operador não é mais complicado do que o anterior:
do actions_if_condition_is_true; while (condition);
Exemplo 10. Esquema do operador do-while
Diferente do while, no do… while, as ações dentro do corpo do laço sempre serão executadas pelo menos uma vez. Mas, fora isso, o comportamento é basicamente o mesmo: o programa executa a ação, verifica a condição para decidir se deve retornar ao início e, se necessário, repete todas as ações do corpo do laço novamente.
Embora esse tipo de laço seja mais conveniente em situações menos frequentes do que no caso do while, ainda existem casos em que é útil. No nosso contexto, um exemplo prático seria a verificação de instrumentos de negociação na janela Market Watch. O algoritmo poderia funcionar da seguinte maneira:
- Pegamos o primeiro instrumento da lista (se tivermos certeza de que ele existe).
- Fazemos o que for necessário com ele (por exemplo, verificamos as ordens pendentes).
- Verificamos se há mais instrumentos na lista.
- Se houver, pegamos o segundo instrumento, depois o terceiro, e assim por diante, até que a lista se esgote.
A tarefa de somar números usando esse tipo de laço ficaria quase igual ao exemplo anterior, com apenas duas pequenas mudanças na estrutura do laço.
//--- Variables declaration int a = 1; // initialize (!) the variable parameter used in the condition int sum = 0; // result string message = "The sum of "+ (string) a; // show message //--- Perform main operations do // Execute { sum += a; // Add value to the sum if ( a != 1) // The first value is already added at the time of initialization { message += " + " + string (a); // Further operations } a++; // Increase parameter (very important!) } while (a <= 5) // While a is less than 5 //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Exemplo 11. Usando o laço do-while
Laço for
O for (para) é o laço mais utilizado, pois é ideal para percorrer sequências e listas quando o número de elementos a serem processados pode ser facilmente calculado. Sua estrutura é organizada da seguinte maneira:
for (initialize_counter ; conditions ; change_counter)
action_if_condition_is_true;
Exemplo 12. Modelo para o laço for
Ao contrário das formas anteriores de laço, no for, fica muito mais claro qual parâmetro está sendo alterado (no exemplo, chamamos esse parâmetro de "contador") e como ele muda ao longo das iterações. Dessa forma, o foco do corpo do laço pode ficar exclusivamente na tarefa principal, como percorrer elementos de um array.
Na prática, o for funciona de maneira semelhante ao while, pois a condição é verificada antes da execução do corpo do laço. A tarefa de somar números poderia ser resolvida da seguinte maneira:
//--- Variables declaration int a; // do NOT initialize the variable parameter, only describe // (this is optional as you can describe it in the loop header) int sum = 0; // result string message = "The sum of "; // a shorter message for the user, // as the value is not yet known //--- Perform main operations for (a=1; a<=5; a++)// For (each `a` from 1 to 5) /loop header/ { sum += a; // Add `a` to the sum if ( a != 1) // the first value does not contain "+" { message += " + "; // add "+" before all sequence members starting from the second } message += string (a); // Add sequence elements to the message // Changes to the counter are described not here, but in the loop header as the last parameter. } //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Exemplo 13. Usando o laço for
Neste exemplo, há mudanças mais significativas em relação aos anteriores (destacadas em amarelo). Um detalhe importante é que todas as etapas do controle do contador (variável que muda a cada iteração) foram colocadas no cabeçalho do laço: Inicialização (a = 1). Verificação da condição (a <= 5). Se a condição for verdadeira, o corpo do laço é executado. Atualização do valor de a (a++), seguida de uma nova verificação da condição.
Operadores break e continue
Às vezes, não precisamos executar todas as etapas de um laço.
Por exemplo, ao procurar um valor dentro de um array, não há necessidade de percorrer todo o array se o valor for encontrado no meio da sequência. Nesse caso, quando queremos interromper a execução do laço imediatamente, usamos o operador break (interromper), que encerra o laço e passa para a próxima instrução após ele. Por exemplo:
string message = ""; for (int i=0; i<=3; i++) { if ( i == 2 ) { break; } message += " " + IntegerToString( i ); } Print(message); // Result: 0 1 (no further execution)
Exemplo 14. Usando o operador break
Além disso, há situações em que não queremos executar o corpo do laço sob determinadas condições, mas o laço não deve ser interrompido — apenas certas iterações devem ser ignoradas. Por exemplo, se no exemplo 14 quisermos evitar imprimir o número 2, mas todas as outras execuções devem continuar normalmente, basta substituir o break por continue (continuar). O operador continue faz com que o programa pule imediatamente para a próxima iteração, ou seja, em laços while e do-while, a execução retorna diretamente à verificação da condição; em laços for, ele salta para a próxima atualização do contador, ignorando o restante do corpo do laço na iteração atual.
string message = ""; for (int i=0; i<=3; i++) { if ( i == 2 ) { continue; } message += " " + IntegerToString( i ); } Print(message); // Output: 0 1 3 (the number 2 is skipped)
Exemplo 15. Usando o operador continue
O operador break pode ser usado dentro de laços e operadores case no switch, enquanto o continue pode ser utilizado somente dentro de laços.
Isso cobre todos os operadores de controle de fluxo. Agora, vejamos como esses operadores podem ser úteis na criação de um programa real.
Algumas palavras sobre o assistente de criação de arquivos (modo de criação de indicadores)
Espero que você já tenha se familiarizado com a criação de indicadores usando o assistente, especialmente após a leitura do primeiro artigo. No entanto, farei uma breve descrição das telas do assistente para criação de indicadores, focando em aspectos diferentes dos abordados anteriormente.
A primeira tela do assistente pode ser ignorada, pois não há nada de novo em relação ao que foi explicado no primeiro artigo. A segunda tela também não traz novidades, mas agora você já sabe quais são as configurações globais do programa, que podem ser configuradas usando parâmetros de entrada. Às vezes, é mais prático inserir esses parâmetros diretamente pelo assistente, já que estamos utilizando a ferramenta de qualquer forma. Lembre-se de que o nome do parâmetro pode ser qualquer um. Eu prefiro adicionar o prefixo inp_, pois isso facilita a identificação do local em que esses parâmetros estão sendo utilizados ao revisar o código.
Figura 5. Adição de parâmetros de programa usando o assistente
O segundo ponto que quero destacar é a escolha do formato do método OnCalculate (veja a imagem 6). Lembre-se de que o método OnCalculate é o principal de qualquer indicador em MQL5. Ele é chamado toda vez que o terminal recalcula o indicador. No assistente, podemos definir quantos e quais parâmetros esse método aceitará.
Figura 6. Escolha do formato do método OnCalculate
A opção superior é a mais utilizada na maioria das situações, pois faz com que a função receba automaticamente os arrays de open, high, low, close, volume e time, permitindo que o usuário trabalhe com esses dados conforme necessário.
No entanto, existem casos especiais em que queremos permitir que o usuário escolha a curva sobre a qual o indicador será calculado. Por exemplo, uma média móvel pode ser calculada com base nos preços de mínima ou máxima, ou até mesmo com base na curva de um indicador já existente no gráfico. Nesses casos, devemos selecionar a opção inferior e, então, o OnCalculate receberá um array contendo os dados da curva selecionada. Além disso, no indicador compilado, será exibida uma aba chamada "Parâmetros", que permitirá ao usuário definir a curva desejada. A imagem 7 mostra as telas iniciais de indicadores configurados com as duas opções diferentes (Upper e Lower).
Figura 7. Comparação das telas iniciais de indicadores com diferentes formas de OnCalculate
Por fim, quero destacar um último detalhe sobre o assistente de criação de indicadores. Na última etapa do assistente, podemos escolher como o indicador será exibido (imagem 8). Para desenhar o indicador no gráfico, é necessário adicionar um "buffer", que nada mais é do que um array especial contendo os dados a serem desenhados. Existem várias formas de exibição: desde uma linha simples (como em uma média móvel) até histogramas multicoloridos e velas personalizadas. Com um pouco mais de esforço, é até possível criar desenhos personalizados, embora essa opção não seja oferecida diretamente pelo assistente.
Figura 8. Parâmetros de exibição do indicador
Se queremos que o indicador desenhe algo e, ao mesmo tempo, garantir que outros programas (como EAs) consigam acessar seus resultados com facilidade, é necessário adicionar pelo menos um buffer.
Um buffer é apenas um array de números que armazena os dados calculados pelo indicador para cada vela. Geralmente, esses dados não precisam ser utilizados para o desenho. Às vezes, buffers são usados para armazenar cores ou valores intermediários necessários para o cálculo de outros buffers.
É possível adicionar os buffers manualmente, mas isso exigiria algumas etapas extras que, embora não sejam difíceis, podem ser um pouco monótonas. Para iniciantes, recomendo adicionar os buffers usando o assistente, pois isso facilita o processo.
Normalmente, ao desenhar linhas, é necessário pelo menos um buffer para cada curva calculada. Para desenhar setas, geralmente são necessários dois buffers — um para cada direção. Embora seja possível usar apenas um buffer, se a seta não indicar uma direção específica e houver apenas um valor calculado por vela, é recomendável usar dois buffers. Existem casos mais complexos, mas abordá-los exigiria a elaboração de outros artigos. Por enquanto, as informações fornecidas neste artigo são suficientes.
Agora, vamos simplesmente criar o indicador com as seguintes configurações:
- Nome: InsideOutsideBar
- Parâmetro único: inp_barsTypeSelector, do tipo int, com valor padrão 0
- Formato da função OnCalculate: opção superior (aquela que recebe um conjunto de arrays) no terceiro passo do assistente
- Dois buffers de exibição: adicionados na seção "Plots" (veja a imagem 8), chamados Up e Down, com o tipo de exibição "Arrow" (seta).
Analisando o código gerado pelo assistente
Se os passos anteriores foram seguidos corretamente, o código gerado será semelhante ao seguinte:
//+------------------------------------------------------------------+ //| InsideOutsideBar.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- plot Up #property indicator_label1 "Up" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrMediumPurple #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot Down #property indicator_label2 "Down" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumPurple #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- input parameters input int inp_barsTypeSelector=0; //--- indicator buffers double UpBuffer[]; double DownBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,UpBuffer,INDICATOR_DATA); SetIndexBuffer(1,DownBuffer,INDICATOR_DATA); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,159); PlotIndexSetInteger(1,PLOT_ARROW,159); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { //--- //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+
Exemplo 16. Código indicador gerado pelo assistente de código MQL5
A primeira parte do código, como de costume, contém o cabeçalho, onde os parâmetros são definidos. No momento, os campos para descrever a autoria do indicador não são tão importantes. Em seguida, há uma linha que informa que o indicador será exibido na janela do gráfico, o que também não é novidade. No entanto, as duas últimas linhas desse bloco informam ao compilador que nosso indicador possui dois buffers de cálculo e dois buffers de exibição (neste caso, os buffers são os mesmos para ambas as funções, mas, em geral, o número de buffers de cálculo e de exibição pode ser diferente):
#property indicator_buffers 2 #property indicator_plots 2
Exemplo 17. Descrição dos buffers de indicadores para cálculo e para desenho
O próximo bloco descreve como os elementos gráficos do indicador devem ser desenhados com base nos buffers:
//--- plot Up #property indicator_label1 "Up" // Display name of the buffers #property indicator_type1 DRAW_ARROW // Drawing type - arrow #property indicator_color1 clrMediumPurple // Arrow color #property indicator_style1 STYLE_SOLID // Line style - solid #property indicator_width1 1 // Line width (arrow size) //--- plot Down #property indicator_label2 "Down" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumPurple #property indicator_style2 STYLE_SOLID #property indicator_width2 1
Exemplo 18. Parâmetros de desenho
Depois disso, temos a declaração dos parâmetros de entrada do indicador. No nosso caso, há apenas um parâmetro, inp_barsTypeSelector:
//--- input parameters input int inp_barsTypeSelector=0;
Exemplo 19. Parâmetros de entrada do indicador
A seguir, o código cria as variáveis dos buffers de exibição. Note que esses buffers são armazenados como arrays dinâmicos do tipo double. Essencialmente, esses arrays contêm os valores de preço nos quais o indicador será desenhado:
//--- indicator buffers double UpBuffer[]; double DownBuffer[];
Exemplo 20. Variável para buffer de desenho
Por fim, encontramos a definição de duas funções importantes: OnInit e OnCalculate.
A função OnInit funciona da mesma maneira que nos scripts MQL5. Ela é chamada automaticamente assim que o indicador inicia, antes de qualquer outra função, e é executada apenas uma vez. No código gerado pelo assistente, essa função contém duas chamadas para a função padrão SetIndexBuffer, que vincula os arrays de dados aos buffers gráficos do indicador. Além disso, os ícones das setas foram configurados usando a função PlotIndexSetInteger. A função OnInit não recebe parâmetros e sempre retorna o status de inicialização bem-sucedida.
Como mencionei anteriormente, OnCalculate é a função chamada a cada novo tick, ou sempre que outros programas tentam acessar o indicador. No momento, essa função está vazia, e será nela que implementaremos a lógica principal do indicador.
Essa função recebe vários parâmetros, que veremos a seguir.
- rates_total – o número total de barras no gráfico.
- prev_calculated – o número de barras que já foram calculadas pelo indicador. Na primeira execução, prev_calculated será zero, mas, como a implementação do assistente faz com que a função retorne o total de barras no tick atual, na próxima chamada, o terminal passará esse valor como prev_calculated. Esse comportamento é usado em um método comum para detectar novas velas: Se rates_total == prev_calculated, então a vela é a mesma e não há necessidade de recalcular nada. Se forem diferentes, significa que uma nova vela apareceu, e podemos prosseguir com os cálculos.
- Arrays de dados: preços, tempos, volumes, spreads… Basicamente, tudo o que um trader pode precisar ao programar um indicador sem depender de outros indicadores externos. Esses arrays são passados por referência (porque não se pode passar arrays por valor), mas possuem o modificador const, o que informa ao compilador que seus dados não podem ser alterados dentro da função.
Agora podemos começar a programar nosso indicador.
Programando o indicador
Primeiramente, faremos algumas configurações básicas. Como nosso indicador só consegue identificar dois tipos de barras (interna e externa), criaremos uma enumeração global após as diretivas do pré-processador:
enum BarsTypeSelector { Inside = 0, // Inside bar Outside = 1 // Outside bar };
Exemplo 21. Enumeração para tipo de cálculo
Em seguida, alteraremos o tipo e o valor padrão do nosso parâmetro de entrada:
//--- input parameters input BarsTypeSelector inp_barsTypeSelector=Inside;
Exemplo 22. Alteração do tipo e do valor padrão de um parâmetro de entrada
Agora, adicionaremos mais algumas opções de personalização. Permitiremos que o usuário modifique o ícone e ajuste a distância entre o ícone e a barra. Para isso, adicionaremos manualmente (para lembrar que adicionar parâmetros é bem simples) duas novas variáveis na seção de parâmetros de entrada:
//--- input parameters input BarsTypeSelector inp_barsTypeSelector=Inside; input int inp_arrowCode=159; input int inp_arrowShift=5;
Exemplo 23. Variáveis adicionais para configurações de aparência
Lembre-se de que parâmetros de entrada, por si só, não afetam o funcionamento do indicador. Eles precisam ser utilizados em algum lugar do código. Para que essas novas configurações passem a funcionar agora mesmo, modificaremos a função OnInit:
int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,UpBuffer,INDICATOR_DATA); SetIndexBuffer(1,DownBuffer,INDICATOR_DATA); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,inp_arrowCode); PlotIndexSetInteger(1,PLOT_ARROW,inp_arrowCode); PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, inp_arrowShift); PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -inp_arrowShift); //--- return(INIT_SUCCEEDED); }
Exemplo 24. Alterações na função OnInit
Isso encerra as configurações visuais. Agora, vamos implementar a lógica principal do indicador na função OnCalculate. Para manter o código mais compacto, omitirei o cabeçalho da função e focarei apenas na parte que realiza os cálculos.
/******************************************************************************************** * * * Attention! All arrays in the code are NOT series, so we have larger numbers on the right. * * * ********************************************************************************************/ //--- Description of variables int i, // Loop counter start; // Initial bar for historical data const int barsInPattern = 2; // For proper preparation we need to know, // how many bars will be involved in one check // I've added a constant to avoid the presence of // "magic numbers" that come from nowhere, // we could use the #define directive instead of the constant //--- Check the boundary conditions and set the origin if(rates_total < barsInPattern) // If there are not enough bars to work return(0); // Do nothing if(prev_calculated < barsInPattern+1)// If nothing has been calculated yet { start = barsInPattern; // Set the minimum possible number of bars to start searching } else { start = rates_total — barsInPattern; // If the indicator has been running for some time, // Just count the last two bars } //--- To avoid strange artifacts on the last candlestick, initialize the last elements of the arrays // with EMPTY_VALUE UpBuffer[rates_total-1] = EMPTY_VALUE; DownBuffer[rates_total-1] = EMPTY_VALUE; //--- for(i = start; i<rates_total-1; i++) // Start counting from the starting position // and continue until there are no more closed barsя // (If we needed to include the last - unclosed - bar, // we would set the condition i<=rates_total-1) { // First, let's clear both indicator buffers (initialize to an empty value) UpBuffer[i] = EMPTY_VALUE; DownBuffer[i] = EMPTY_VALUE; if(inp_barsTypeSelector==Inside) // If the user wants to display inner bars { // Check if the current bar is inner if(high[i] <= high[i-1] && low[i] >= low[i-1]) { // And if yes, we mark the previous (larger) candlestick UpBuffer[i-1] = high[i-1]; DownBuffer[i-1] = low[i-1]; } } else // If external bars are needed { // Check if the current bar is external if(high[i] >= high[i-1] && low[i] <= low[i-1]) { // Mark the current candlestick if necessary UpBuffer[i] = high[i]; DownBuffer[i] = low[i]; } } } //--- return value of prev_calculated for the next call return(rates_total);
Exemplo 25. Corpo da função OnCalculate
Acredito que o código está bem comentado e não há necessidade de uma explicação linha por linha. Mas, se houver dúvidas, deixem perguntas nos comentários. Lembre-se de inserir esse código dentro da função OnCalculate, substituindo todo o conteúdo anterior (incluindo o return, que já está no final do exemplo). O código-fonte completo e funcional do indicador está anexado ao artigo.
Na imagem 9, podemos ver o indicador em funcionamento. À esquerda, em rosa, estão as barras anteriores às barras internas. À direita, estão os barras externas.
Figura 9. Funcionamento do indicador InsideOutsideBar À esquerda estão as barras internas, à direita estão as barras externas
Considerações finais
Agora que você dominou os operadores descritos neste artigo, será capaz de compreender a maioria dos programas escritos em MQL5 e também escrever seus próprios algoritmos, independentemente da complexidade. Basicamente, toda a programação pode ser reduzida a três conceitos fundamentais: Operações básicas – como cálculos aritméticos e atribuições. Escolhas condicionais – usando if ou switch para decidir entre diferentes caminhos. Repetições controladas – usando laços para repetir um bloco de código quantas vezes for necessário. Além disso, funções são uma maneira conveniente de organizar operações, e objetos permitem estruturar funções e seus dados externos de maneira eficiente.
Agora, você definitivamente não é mais um iniciante! No entanto, há um caminho a percorrer até se tornar um programador profissional em MQL5. Por exemplo, será necessário aprender a integrar indicadores já existentes aos seus programas e a criar Expert Advisors (EAs) para automatizar operações de trading. Além disso, a plataforma MQL5 possui muitos recursos avançados que podem ser úteis para personalizar completamente o ambiente de desenvolvimento. Com ela, você pode criar: interfaces gráficas para seus EAs; indicadores inovadores, como Kagi ou Renko; ferramentas práticas para análise de mercado; estratégias automatizadas lucrativas. Por isso, este ciclo de artigos continuará. No próximo artigo, abordarei como escrever EAs de forma detalhada. Depois disso, falarei sobre Programação Orientada a Objetos (POO) no contexto do MQL5. A partir daí, a parte geral do curso estará concluída, e então poderei aprofundar detalhes da plataforma e explorar a biblioteca padrão do MQL5, localizada na pasta Include.
Lista de artigos anteriores da série:
- Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar
- Aprendendo MQL5 do iniciante ao profissional (Parte II): Tipos de dados básicos e uso de variáveis
- Aprendendo MQL5 do iniciante ao profissional (Parte III): Tipos de dados complexos e arquivos inclusos
- Aprendendo MQL5 do iniciante ao profissional (Parte IV): Sobre arrays, funções e variáveis globais do terminal
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/15499
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.





- 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
Bom dia, Fedorov,
Agradeço o esforço em seus artigos: estão me resultando muito úteis.
Saludos,
Bom dia, Fedorov,
Agradeço o esforço em seus artigos: estão me resultando muito úteis.
Saludos,