
MQL5: Crie o seu próprio indicador
Introdução
O que é um indicador? É um conjunto de valores calculados que deseja-se que sejam exibidos em uma tela de forma conveniente. Os conjuntos de valores são representados em programas como séries. Deste modo, a criação de um indicador significa escrever um algorítimo que manuseia algumas séries (séries de preço) e registra os resultados do manuseamento para outras séries (valores de indicador).
Apesar do fato de que há muitos indicadores prontos, os quais já tornaram-se clássicos, a necessidade de criar os próprios indicadores de alguém sempre existirá. Tais indicadores que criamos utilizando nossos próprios algorítimos chamam-se indicadores personalizados. Neste artigo, discutiremos como criar um indicador personalizado simples.
Os indicadores são diferentes
Um indicador pode ser apresentado como linhas ou áreas coloridas ou pode ser exibido como etiquetas especiais para a posição de entrada. Estes tipos também podem ser combinados, o que fornece ainda mais tipos de indicadores. Consideraremos a criação de um indicador no exemplo bem conhecido True Strength Index desenvolvido por William Blau.
True Strength Index
O indicador TSI é baseado no ímpeto duplamente suavizado de identificar tendências, bem como áreas sobre-vendidas/sobre-compradas. A explicação matemática disto pode ser encontrada em Momentum, Direction, and Divergence por William Blau. Aqui incluímos apenas sua fórmula de cálculo.
TSI(CLOSE,r,s) =100*EMA(EMA(mtm,r),s) / EMA(EMA(|mtm|,r),s)
na qual:
- mtm = CLOSEatual – CLOSprev, série de valores que denotam a diferença entre os preços de fechamento da barra atual e aquele da barra anterior;
- EMA(mtm,r) = suavização exponencial de valores mtm com a extensão do período igual a r;
- EMA(EMA(mtm,r),s) = suavização exponencial de valores EMA (mtm,r) com o período s;
- |mtm| = valores mtm absolutos;
- r = 25,
- s = 13.
A partir desta fórmula, podemos extrair três parâmetros que influenciam o cálculo do indicador. Estes são os períodos r e s, assim como os tipos de preços usados para os cálculos. Em nosso caso, utilizamos o preços de FECHAMENTO.
Assistente do MQL5
Vamos exibir o TSI como uma linha azul - aqui precisamos inicializar o assistente do MQL5. No primeiro estágio, devemos indicar o tipo de um programa que queremos criar - indicador personalizado. Em um segundo estágio, vamos definir o nome do programa, os parâmetros r e s e os seus valores.
Depois disso, vamos definir que o indicador deverá ser exibido em uma janela separada como uma linha azul e ajustar a etiqueta to TSI para esta linha.
Todos os dados iniciais foram inseridos, então pressionamos Done (concluído) e obtemos um esboço de nosso indicador.
//+------------------------------------------------------------------+ //| True Strength Index.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //---- plot TSI #property indicator_label1 "TSI" #property indicator_type1 DRAW_LINE #property indicator_color1 Blue #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- input parameters input int r=25; input int s=13; //--- indicator buffers double TSIBuffer[]; //+------------------------------------------------------------------+ //| Função de inicialização do indicador personalizado | //+------------------------------------------------------------------+ int OnInit() { //--- mapeamento de buffers do indicador SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA); //--- return(0); } //+------------------------------------------------------------------+ //| Função de iteração do indicador personalizado | //+------------------------------------------------------------------+ 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[]) { //--- //--- retorna o valor de prev_calculated para o próximo uso return(rates_total); } //+------------------------------------------------------------------+
O assistente do MQL5 cria o cabeçalho do indicador, no qual ele escreve as propriedades do indicador, a saber:
- o indicador é exibido em uma janela separada;
- o número de buffers do indicador, indicator_buffers=1;
- número de tramas, indicator_plots= 1;
- o nome da trama nº 1, indicator_label1="TSI";
- estilo da primeira trama - linha, indicator_type1=DESENHAR_LINHA;
- a cor da trama nº 1, indicator_color1=Azul;
- estilo de uma linha, indicator_style1=ESTILO_SÓLIDO;
- largura da linha para a trama 1, indicator_width1=1.
Todas as preparações estão prontas, agora podemos refinar e melhorar o nossos código.
OnCalculate()
A função OnCalculate() é o manipulador do evento Calcular, que aparece quando é necessário calcular novamente os valores do indicador e desenhá-lo novamente no quadro. Este é o evento de um novo recebimento de seleção, atualização de histórico de símbolo, etc. Por isso o código principal de todos os cálculos dos valores do indicador devem estar localizados exatamente nesta função.
É claro, cálculos auxiliares podem ser implementados em outras funções separadas, mas essas funções adicionais devem ser usadas no manipulador OnCalculate.
Por padrão, o assistente do MQL5 cria a segunda forma do OnCalculate(), o qual fornece acesso a todos os tipos de series temporais:
- Preços aberto, alto, baixo e de fechamento;
- volumes (real e/ou assinalados);
- distribuição;
- tempo de abertura do período.
Mas em nosso caso, precisamos apenas de um array de dados, por isso, vamos alterar a primeira forma de chamar o OnCalculate().
int OnCalculate (const int rates_total, // size of the price[] array const int prev_calculated, // número de barras disponíveis na chamada anterior const int begin, // de onde os dados do price[] começa const double& price[]) // array em que o indicador será calculado { //--- //--- retorna o valor de prev_calculated para o próximo uso return(rates_total); }
Isto nos possibilitará aplicar mais adiante o indicador não apenas aos dados de preço, mas também criar um indicador com base em valores de outros indicadores.
Se selecionarmos Close na aba de parâmetros (é oferecido por padrão), em seguida o price[] passado ao OnCalculate() conterá os preços de fechamento. Se selecionarmos, por exemplo, Preço Típico, o price[] conterá os preços de (Alto+Baixo+Fechamento)/3 para cada período.
O parâmetro rates_total denota o tamanho da série price[]; será útil para organizar os cálculos em um ciclo. A indexação dos elementos no price[] inicia a partir de zero e é direcionado do passado para o futuro. Por exemplo, o elemento price[0] contém o valor mais antigo, enquanto o price[rates_total-1] contém o elemento da série mais atual.
Organizando buffers de indicador auxiliar
Apenas uma linha será mostrada em um quadro, ou seja, os dados de uma série de indicadores. Mas antes disso, precisamos organizar os cálculos intermediários. Os dados intermediários são armazenados em séries de indicadores que são marcados pelo atributo CÁLCULO DE_INDICADORES . A partir da fórmula vemos que precisamos de séries adicionais:
- para valores mtm - série MTMBuffer[];
- para valores |mtm| - série AbsMTMBuffer[];
- para EMA(mtm,r) - série EMA_MTMBuffer[];
- para EMA(EMA(mtm,r),s) - série EMA2_MTMBuffer[];
- para EMA(|mtm|,r) - série EMA_AbsMTMBuffer[];
- para EMA(EMA(|mtm|,r),s) - série EMA2_AbsMTMBuffer[].
No total precisamos acrescentar 6 séries a mais de tipo duplo em nível global e vincular estas séries com os buffers do indicador na função OnInit(). Não esqueça de indicar o novo número de buffers do indicador; a propriedade do indicator_buffers deve ser igual a 7 (havia 1 e foram adicionados 6 buffers a mais).
#property indicator_buffers 7
Agora o código do indicador se parece com isto:
#property indicator_separate_window #property indicator_buffers 7 #property indicator_plots 1 //---- plot TSI #property indicator_label1 "TSI" #property indicator_type1 DRAW_LINE #property indicator_color1 Blue #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- input parameters input int r=25; input int s=13; //--- indicator buffers double TSIBuffer[]; double MTMBuffer[]; double AbsMTMBuffer[]; double EMA_MTMBuffer[]; double EMA2_MTMBuffer[]; double EMA_AbsMTMBuffer[]; double EMA2_AbsMTMBuffer[]; //+------------------------------------------------------------------+ //| Função de inicialização do indicador personalizado | //+------------------------------------------------------------------+ int OnInit() { //--- mapeamento de buffers do indicador SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA); SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS); //--- return(0); } //+------------------------------------------------------------------+ //| Função de iteração do indicador personalizado | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, // size of the price[] array; const int prev_calculated,// número de barras disponíveis; // na chamada anterior; const int begin, // de onde os dados // do price[] começa; const double& price[]) // array em que o indicador será calculado; { //--- //--- retorna o valor de prev_calculated para o próximo uso return(rates_total); }
Cálculos intermediários
É muito fácil organizar os cálculos dos valores dos buffers MTMBuffer[] e AbsMTMBuffer[]. No loop, um por um passam por valores a partir do price[1] até o price[rates_total-1] e escrevem a diferença dentro de uma série e o valor absoluto da diferença dentro de um segundo.
//--- calcula os valores de mtm e |mtm| for(int i=1;i<rates_total;i++) { MTMBuffer[i]=price[i]-price[i-1]; AbsMTMBuffer[i]=fabs(MTMBuffer[i]); }
O próximo estágio é o cálculo da média exponencial destas séries. Existem duas maneiras de fazer isto. Na primeira, escrevemos o algoritmo inteiro tentando não cometer erros. No segundo caso, utilizamos funções prontas que já estão depuradas e intencionadas exatamente para estes propósitos.
No MQL5 não há funções integradas para calcular médias móveis dos valores de séries, mas há uma biblioteca pronta de funções (MovingAverages.mqh). O caminho completo para ela é terminal_directory/MQL5/Include/MovingAverages.mqh, no qual o terminal_directory é o lugar no qual o terminal MetaTrader 5 está instalado. A biblioteca está em um arquivo include; ele contém funções para calcular médias móveis em séries que utilizam um dos quatro métodos clássicos:
- média simples;
- média exponencial;
- média suavizada;
- média ponderada linear.
De modo a utilizar estas funções, em qualquer programa MQL5, acrescente o seguinte no cabeçalho do código:
#include
Precisamos da função ExponentialMAOnBuffer(), a qual calcula a média móvel suavizada na série de valores e registra os valores da média dentro de outra série.
A função de suavizar uma série
No total, o arquivo include MovingAverages.mqh contém oito funções que podem ser divididas em dois grupos de funções do mesmo tipo, cada uma contendo 4 delas. O primeiro grupo contém funções que recebem uma série e simplesmente retornam um valor de uma média móvel em uma posição especificada:
- SimpleMA() - para calcular o valor de uma média simples;
- ExponentialMA() - para calcular o valor de uma média exponencial;
- SmoothedMA() - para calcular o valor de uma média suavizada;
- LinearWeightedMA() - para calcular o valor de uma média ponderada linear.
Estas funções são destinada a obter o valor de uma média uma vez para uma série e não estão otimizadas para chamadas múltiplas. Caso necessite utilizar uma função a partir deste grupo em um loop (para calcular valores de uma média e ainda escrever cada valor calculado dentro de uma série), será necessário organizar um algoritmo otimizado.
O segundo grupo de funções é destinado a preencher a série recipiente com valores de uma série móvel com base na série de valores iniciais:
- SimpleMAOnBuffer() - preenche o buffer[] com valores de uma média simples a partir da série price[];
- ExponentialMAOnBuffer() - preenche o buffer[] com valores de uma média exponencial a partir da série price[];
- SmoothedMAOnBuffer() - preenche o buffer[] com valores de uma média suavizada a partir da série price[];
- LinearWeightedMAOnBuffer() - preenche o buffer[] com valores de uma média ponderada linear a partir da série price[];
Todas as funções especificadas, exceto para buffer[], price[] e o período período de média, obtém 3 ou mais parâmetros, a finalidade dos quais é análoga aos parâmetros da função OnCalculate() - rates_total, prev_calculated e begin. As funções deste grupo processam corretamente séries passadas de price[] e buffer[], levando em consideração a direção de indexação (marcadorAS_SERIES ).
O parâmetro begin indica o índice de uma série de origem, a partir da qual os dados significantes começam, ou seja, dados que precisam ser manipulados. Para a série MTMBuffer[] os dados reais começam com o índice 1, pois o MTMBuffer[1]=price[1]-price[0]. O valor de MTMBuffer[0] é indefinido, por isso begin=1.
//--- primeiro cálculo ExponentialMAOnBuffer(rates_total,prev_calculated, 1, // índice, a partir do qual os dados estão disponíveis para suavizar r, // período da média exponencial MTMBuffer, // buffer para calcular a média EMA_MTMBuffer); // buffer para colocar a média calculada ExponentialMAOnBuffer(rates_total,prev_calculated, 1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);
Ao se fazer uma média, o valor do período deve ser levado em consideração, pois na série de saída os valores calculados são preenchidos com um pequeno atraso, que é maior em períodos de média maiores. Por exemplo, se o período=10, os valores da série resultante começarão com início+período-1=início+10-1. Em chamadas adicionais de buffer[], deve ser levado em consideração, e deve-se iniciar o manuseamento com o índice início+período-1.
Desta forma, é possível obter a segunda média exponencial a partir das séries de MTMBuffer[] e AbsMTMBuffer:
//--- calcula a segunda média móvel ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_MTMBuffer,EMA2_MTMBuffer); ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);
O valor de begin agora é igual a R, porque begin = 1 + r-1 (r é o período da média exponencial primária, tratamento começa com o índice 1). Nas matrizes EMA2_MTMBuffer[] e EMA2_AbsMTMBuffer[], os valores são calculados a partir do índice r + s-1, porque nós começamos a lidar com matrizes de entrada com o índice r, e o período para a segunda média exponencial é igual a s.
Todos os pré-cálculos estão prontos, agora podemos calcular os valores do buffer do indicador, TSIBuffer[], que serão plotados no gráfico.
//--- agora calcular os valores do indicador para(int i=r+s-1;i<rates_total;i++) { TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i]; }Compile o código pressionando a tecla F5 e inicie-a no terminal MetaTrader 5. Funciona!
Ainda restam algumas perguntas.
Otimizando cálculos
Na verdade, não é suficiente apenas escrever um indicador que funcione. Se olharmos detalhadamente para a implementação atual do OnCalculate(), veremos que não está otimizado.
int OnCalculate (const int rates_total, // tamanho da série price[]; const int prev_calculated,// número de barras disponíveis; // na chamada anterior; const int begin,// e onde os dados // do price[] começa; const double &price[]) // array em que o indicador será calculado; { //--- calcula os valores de mtm e |mtm| MTMBuffer[0]=0.0; AbsMTMBuffer[0]=0.0; for(int i=1;i<rates_total;i++) { MTMBuffer[i]=price[i]-price[i-1]; AbsMTMBuffer[i]=fabs(MTMBuffer[i]); } //--- calcula a primeira média móvel ExponentialMAOnBuffer(rates_total,prev_calculated, 1, // índice, a partir do qual os dados estão disponíveis para suavizar r, // período da média exponencial MTMBuffer, // // buffer para calcular a média EMA_MTMBuffer); // buffer para colocar a média calculada ExponentialMAOnBuffer(rates_total,prev_calculated, 1,r,AbsMTMBuffer,EMA_AbsMTMBuffer); //--- calcula a segunda média móvel ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_MTMBuffer,EMA2_MTMBuffer); ExponentialMAOnBuffer(rates_total,prev_calculated, r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer); //--- calcula o valor do indicador for(int i=r+s-1;i<rates_total;i++) { TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i]; } //--- retorna o valor de prev_calculated para o próximo uso return(rates_total); }
No início de cada função, calculamos valores das séries de MTMBuffer[] e AbsMTMBuffer[]. Neste caso, se o tamanho do price[] for igual a centenas de milhares ou até mesmo milhões, os cálculos repetidos desnecessários podem ocupar todos os recursos da CPU, não importa o quanto ela seja potente.
Para organizar cálculos otimizados, utilizamos o parâmetro de entrada prev_calculated, que é igual ao valor retornado pelo OnCalculate() na chamada anterior. Na primeira chamada de cada função, o valor do prev_calculated é sempre igual a 0. Neste caso, calculamos todos os valores no buffer do indicador. Na próxima chamada não teremos que calcular o buffer inteiro - apenas o último valor será calculado. Vamos escrevê-lo desta forma:
//--- se este é o primeiro cálculo if(prev_calculated==0) { //--- coloque zero no inicio MTMBuffer[0]=0.0; AbsMTMBuffer[0]=0.0; } //--- calcula os valores de mtm e |mtm| int start; if(prev_calculated==0) start=1; // comece a preencher MTMBuffer[] e AbsMTMBuffer[] do índice 1 else start=prev_calculated-1; // define o início igual ao último índice nas matrizes for(int i=start;i<rates_total;i++) { MTMBuffer[i]=price[i]-price[i-1]; AbsMTMBuffer[i]=fabs(MTMBuffer[i]); }
Os blocos de cálculo de EMA_MTMBuffer[], EMA_AbsMTMBuffer[], EMA2_MTMBuffer[] e EMA2_AbsMTMBuffer[] não requerem otimização de cálculos, pois o ExponentialMAOnBuffer() já está escrito da maneira otimizada. Precisamos otimizar apenas o cálculo dos valores da série TSIBuffer[]. Utilizamos o mesmo método que foi utilizado para o MTMBuffer[].
//--- calcula os valores do indicador if(prev_calculated==0) start=r+s-1; // define o início do índice nas matrizes for(int i=start;i<rates_total;i++) { TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i]; } //--- retorna o valor de prev_calculated para o próximo uso return(rates_total);
A última observação para o procedimento de otimização: O OnCalculate() retorna o valor do rates_total. Isto significa que o número de elementos na série de entrada price[] é utilizada para o cálculo de indicadores.
O valor retornado pelo OnCalculate() é salvo na memória do terminal e na próxima chamada do OnCalculate() que é passado para a função como o valor do parâmetro de entrada prev_calculated.
Isto possibilita que sempre se saiba o tamanho da série de entrada na chamada anterior do OnCalculate() e iniciar o cálculo dos buffers do indicador a partir de um índice correto sem recálculos desnecessários.
Verificando os dados de entrada
Há mais uma coisa que precisamos fazer para que o OnCalculate() funcione perfeitamente. Vamos acrescentar a verificação da série de price[], na qual os valores do indicador são calculados. Se o tamanho da série (rates_total) for pequeno demais, nenhum cálculo será necessário - precisamos esperar até a próxima chamada do OnCalculate(), quando os dados não forem suficientes.
//--- se o tamanho de price[] é muito pequeno if(rates_total<r+s) return(0); // não calcula ou desenha alguma coisa //--- se esta é a primeira chamada if(prev_calculated==0) { //--- coloque zero no inicio MTMBuffer[0]=0.0; AbsMTMBuffer[0]=0.0; }
Já que o suavizamento exponencial é utilizado duas vezes sequencialmente para calcular o True Strength Index, o tamanho do price[] deve ser no mínimo igual ou maior do que a soma dos períodos r e s; do contrário, a execução é interrompida e o OnCalculate() retorna 0. O valor zero retornado significa que o indicador não será traçado no quadro, pois seus valores não são calculados.
Configurando a representação
No que diz respeito à exatidão dos cálculos, o indicador está pronto para uso. Mas se o chamarmos a partir de outro programa mql5, ele será constituído, por padrão, de preços de fechamento. Podemos especificar outro tipo de preço padrão - especificar um valor a partir da enumeração ENUM_APPLIED_PRICE na propriedade indicator_applied_price de um indicador.
Por exemplo, a fim de definir um preço típico ( (alto+baixo+fechamento)/3) para um preço, vamos escrever o seguinte:
#property indicator_applied_price PRICE_TYPICAL
Se estivermos planejando utilizar apenas seus valores utilizando as funções iCustom() ou IndicatorCreate() nenhum outro aperfeiçoamento é necessário. Mas se for usado de forma direta, ou seja, traçado no quadro, são recomendados ajustes adicionais:
- número da barra, iniciando a partir do qual um indicador é traçado;
- Etiquetas para valores em TSIBuffer[], os quais serão refletidos na DataWindow;
- nome curto do indicador, mostrado em uma janela separada e no pop-up de ajuda ao apontar o cursor do mouse sobre a linha do indicador;
- o número de dígitos depois do ponto decimal que é mostrado nos valores do indicador (isto não afeta a precisão).
Estes ajustes podem ser refinados no manipulador OnInit(), utilizando as funções a partir do grupo Custom Indicators . Acrescente novas linhas e salve o indicador como True_Strength_Index_ver2.mq5.
//+------------------------------------------------------------------+ //| Função de inicialização do indicador personalizado | //+------------------------------------------------------------------+ int OnInit() { //--- mapeamento de buffers do indicador SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA); SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS); //--- barra, começando pela qual o indicador é desenhado PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s-1); string shortname; StringConcatenate(shortname,"TSI(",r,",",s,")"); //--- define um rótulo para exibir em DataWindow PlotIndexSetString(0,PLOT_LABEL,shortname); //--- define um nome para mostrar em uma sub-janela ou pop-up IndicatorSetString(INDICATOR_SHORTNAME,shortname); //--- define a acurácia mostrada nos valores do indicador IndicatorSetInteger(INDICATOR_DIGITS,2); //--- return(0); }
Se iniciarmos ambas versões do indicador e rolar o quadro até o início, veremos todas as diferenças.
Conclusão
Com base no exemplo de criar o indicador True Strength Index, podemos delinear os momentos básicos no processo de escrever qualquer indicador em MQL5:
- Para criar o seu próprio indicador customizado, utilize o Assistente de MQL5 que o ajudará a executar as operações de rotina preliminares no ajuste do indicador. Selecione a variável necessária da função OnCalculate().
- Se for necessário, acrescente mais séries para cálculos intermediários e as vincule com os buffers do indicador necessário utilizando a função SetIndexBuffer() . Indique o tipo do INDICATOR_CALCULATIONS para estes buffers.
- Otimize os cálculos no OnCalculate(), pois esta função será chamada cada vez que os dados de preço mudarem. Utilize funções já depuradas para tornar a escrita do código mais fácil, e para melhor legibilidade.
- Execute refinamentos visuais adicionais do indicador para tornar o programa fácil de usar, tanto para outros programas mql5 ou pelos usuários.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10





- 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
No trecho final da função OnCalculate o comando de loop "for" foi traduzido do inglês e está escrito "para", como evidente abaixo. Sugiro consertar para evitar erros com principiantes em programação.
No trecho final da função OnCalculate o comando de loop "for" foi traduzido do inglês e está escrito "para", como evidente abaixo. Sugiro consertar para evitar erros com principiantes em programação.
Ótima observação, Leo =)