![Os princípios do cálculo econômico de indicadores](https://c.mql5.com/i/articles/overlay.png)
Os princípios do cálculo econômico de indicadores
Introdução
A ideia de preservação de recursos em um ou outro segmento de atividades práticas das pessoas - é talvez um dos tópicos mais significantes e urgentes no caminho para o desenvolvimento e progresso humano. Programar no MQL5, nesse respeito, não é exceção. Claro, se a gama de tarefas é limitada apenas a negociação visual, então muitas falhas de programação podem permanecer despercebidas.
Mas tudo que se relaciona a negociação automática, inicialmente necessita da escrita de um código maximamente econômica, caso contrário, o processo de teste e otimização dos robôs de negociação pode ser esticado por um período de tempo tão longo que seria virtualmente impossível esperar até ser completado. A ideia de criar algo de valor nesse tipo de situação parece bem duvidoso.
Então, antes de embarcar na implementação de estratégias de negociação, faria sentido ficar mais familiarizado com esses detalhes de programação, que possuem em efeito no tempo de otimização e teste do consultor especialista. E uma vez que a maioria dos consultores especialistas contém chamadas para os indicadores do usuário e seus códigos, então suponho que devemos começar com eles.
Em geral, não há muitos pontos relevantes, que devem ser lembrados quando construindo indicadores, então, é mais lógico simplesmente revisar cada um deles em ordem.
Recálculo em cada ponto indicador destas, que ainda não foram calculadas, novas barras que surgiram nos indicadores clássicos
A própria essência dos indicadores clássicos, como RSI, ADX, ATR, CCI, etc., é que em barras fechadas, o cálculo desses indicadores pode ser feito apenas uma vez, e após isso cálculos podem apenas ser feitos em novas barras que surgem. A única exceção é a barra aberta atual, na qual a computação é feita repetidamente em cada ponto, até que essa barra se feche.
A maneira mais fácil de descobrir se é razoável calcular os indicadores nas barras não calculadas - é comparar no verificador de estratégia, os resultados de executar tais indicadores (otimizados) com os indicadores, calculados em todas as barras, a todo o tempo (não otimizados).
Isso é feito bem facilmente. Um consultor especialista é criado com a função vazia de OnInit () e OnTick (). Tudo que você precisa fazer é escrever a chamada no consultor especialista da versão exigida para o indicador otimizado ou não otimizado, e admirar os resultados das execuções no verificador de tais especialistas em ambos os casos. Como um exemplo, vou tomar o indicador SMA.mq5 do meu artigo "Indicadores Personalizados no MQL5 para novatos", no qual farei a substituição de uma linha.
if (prev_calculated == 0) // if this is the first start, then make a recalculation of all existing bars first = MAPeriod - 1 + begin; else first = prev_calculated - 1; // on all subsequent starts, make calculations only on newly appeared bars
em
first = MAPeriod - 1 + Begin; / / On all ticks the bars are being recalculated
Como resultado, consigo uma versão não otimizada do código de programação (SMA !!!!!!. mq5), que, ao contrário do original, recalculará todos os seus valores em cada ponto. Falando estritamente, versões do código especialista em ambos os casos são praticamente idênticas, então fornecerei apenas uma delas (SMA_Test.mq5)
//+------------------------------------------------------------------+ //| SMA_Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" int Handle; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //----+ //----+ Obtaining the indicator handle Handle = iCustom(Symbol(), 0, "SMA"); if (Handle == INVALID_HANDLE) Print(" Not able to obtain the indicator handle SMA"); //----+ return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //----+ //--- Release the indicator handle IndicatorRelease(Handle); //----+ } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //----+ double SMA[1]; //----+ Using the indicator handle, copy the values of the indicator // buffer in a specially prepared for this statistical array CopyBuffer(Handle, 0, 0, 1, SMA); //----+ } //+------------------------------------------------------------------+
Podemos começar os testes gora. Deve-se observar que em todos s testes nesse artigo, utilizaremos um modo de simulação de mudanças de barras, que é o mais próximo possível da realidade - "Cada ponto"!
Aqui está o resultado de executar um indicador otimizado no verificador:
A cor vermelha indica o tempo durante o qual o teste foi passado. Não se pode dizer que isso é muito! Mas para completar a verificação do indicador SMA !!!!!!. mq5 tivemos que esperar um tempo muito longo!
Basicamente, o tempo do processamento do teste nesse caso difere do teste anterior por mais de 500 vezes. E isso é além do fato de que um período suficientemente curto de tempo foi selecionado. Mas enquanto durante o teste do Consultor Especialista podemos tolerar gastos computacionais tão grandes, é melhor esquecermos a otimização de seus parâmetros!
E, portanto, essa é a evidência mais eloquente do fato de que um código escrito economicamente - não é apenas um passatempo para profissionais no campo da programação, mas uma abordagem bem tópica da escrita do seu próprio código.
Na internet há toda uma página da internet Overclockers.ru dedicada à aceleração de computadores pessoais de forma a maximizar o seu desempenho. O meio básico dessa prática é o uso de componentes de computador muito mais caros para aumentar a velocidade de processamento da CPU e da memória RAM.
Após isso ser feito, um sistema de resfriamento por água mais caro, ou mesmo imersão em um processador de nitrogênio liquido, é utilizado para essa CPU com overclock. O resultado de tais atos é um aumento de duas ou três vezes do desempenho do computador.
Uma escrita competente, econômica do código pode frequentemente nos ajudar a alcançar muito, enquanto aplicando muito pouco esforço. Claro, esse método não consegue transformar um Celleron300A em um Core 2 Quad Q6600, no entanto, ele nos permite fazer um PC regular, com orçamento padrão, funcionar de uma forma que somente seria esperada de um computador com configuração de primeira linha!
Recálculo repetido da barra fechada atual em alguns, não tão clássicos, indicadores
E tudo seria simplesmente ótimo se esse método de otimização do código do programa fosse adequado para todos os indicadores indiscriminadamente! Mas, infelizmente, isso não é verdade. Há todo um grupo de indicadores que sob tal abordagem começam a normalmente calcular apenas uma vez, durante o carregamento do indicador nos dados históricos já existentes.
E em todas as barras que surgiram após o carregamento do indicador, seus valores acabaram sendo totalmente incorretos. A razão principal de isso acontecer é que algumas variáveis do código do indicador são dependentes desses indicadores, que possuíam as mesmas variáveis após o cálculo do indicador na barra anterior. Oficialmente, aparecerá como segue:
AlgumaVariável(barra) = Função(AlgumaVariável(barra - 1))
onde:
- AlgumaVariável() — o valor de alguma variável para alguma barra;
- barra - número da barra, na qual o cálculo é feito.
Por razões óbvias, no código de fato, tais dependências possuem uma forma funcional menos clara. Mas a essência disso não muda, por exemplo, para mover o T3 (indicador não otimizado - T3 !!!!!!. mq5) a parte do código, relevante para nós, parece com isso
e1 = w1 * series + w2 * e1;
e2 = w1 * e1 + w2 * e2;
e3 = w1 * e2 + w2 * e3;
e4 = w1 * e3 + w2 * e4;
e5 = w1 * e4 + w2 * e5;
e6 = w1 * e5 + w2 * e6;
//----
T3 = c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3;
As variáveis e1, e2, e3, e4, e5, e6 possuem precisamente tal dependência funcional, que envolve o uso desse código para o cálculo de cada barra nova apenas uma vez! Mas a barra atual, através de um cálculo semelhante, será pulada constantemente até que seja fechada.
E os valores dessas variáveis na barra atual mudarão o tempo todo, apesar de que para a barra atual, antes de mudá-la, eles devem permanecer como eram antes do cálculo na barra anterior!
E, portanto, os valores dessas variáveis na barra anterior (com relação à barra atual) devem ser salvos nas variáveis estáticas, e transferí-los para serem reutilizados até a próxima modificação na barra, na qual os penúltimos valores das variáveis devem ser novamente salvos e1, e2, e3, e4, e5 , e6.
O código adicional, que realiza manipulações similares com os valores, é bem simples. Primeiramente, você deve declarar as variáveis estatísticas locais para armazenar valores dentro da função OnCalculate ()
//---- declare the static variables for storing the valid values of the coefficients static double e1_, e2_, e3_, e4_, e5_, e6_;
Após isso, fazer a memorização dos valores das variáveis no ciclo na barra atual antes de quaisquer cálculos, no momento em que o número de barras recém surgidas é maior que zero:
//---- memorize the values of variables before the run on the current bar if (rates_total != prev_calculated && bar == rates_total - 1) { e1_ = e1; e2_ = e2; e3_ = e3; e4_ = e4; e5_ = e5; e6_ = e6; }
E antes do bloco do operador do ciclo, restaure os valores das variáveis por transformação inversa:
//---- restore the values of the variables
e1 = e1_;
e2 = e2_;
e3 = e3_;
e4 = e4_;
e5 = e5_;
e6 = e6_;
Muito naturalmente, o começo de inicialização dos coeficientes calculados são agora feitos apenas uma vez, no primeiro inicio da função OnCalculate (), e agora a inicialização é feita não dos coeficientes em si, mas das variáveis estáticas correspondentes.
//---- calculating the starting number first for the cycle of recalculation of bars if (prev_calculated == 0) // verification of the first start of the indicator calculation { first = begin; // the starting number for calculating all of the bars //---- the starting initialization of calculated coefficients e1_ = price[first]; e2_ = price[first]; e3_ = price[first]; e4_ = price[first]; e5_ = price[first]; e6_ = price[first]; }
Como resultado, o indicador final T3.mq5 começou a fazer cálculos da forma mais rentável. Tudo não seria nada, mas não é sempre que dependências funcionais similares podem ser tão facilmente identificadas. Nesse caso, os valores de todas as variáveis dos indicadores podem ser memorizados em variáveis estáticas, e restaurados da mesma forma.
E apenas depois podemos começar a descobrir quais variáveis realmente precisam ser recuperadas, e para quais não há necessidade. Para fazer isso, precisamos pendurar no gráfico as versões otimizadas e não otimizadas do indicador e verificar o trabalho delas, gradualmente, removendo da lista de recuperação uma variável de cada vez. No final, ficamos com apenas aquelas variáveis que realmente precisam ser recuperadas.
Naturalmente, forneci essa versão de lógica para trabalhar com o código do programa de indicadores normais, nos quais há uma recontagem da barra atual e das barras que surgiram recentemente. Para indicadores, que redesenham e olham para o futuro, não seremos capazes de criar um método do código análogo, muito padrão e simples, devido a traços únicos desses indicadores. E a maioria dos escritores de Consultores Especialistas não veem necessidade real para isso. Portanto, é aí que podemos considerar a análise detalhada desses indicadores completa.
Características de chamadas para indicadores, que podem fazer o código do MQL5 excessivamente lento
Parece que a tarefa está completa, otimizamos o indicador, que conta barras na forma mais econômica, e agora é suficiente escrever algumas linhas de código e chamar esse indicador no código do consultor especialista ou do indicador para obter os valores calculados do buffer do indicador.
Mas isso não é tão fácil quanto pode parecer se abordado de uma maneira formal, sem se importar em descobrir que tipo de operações estão por trás dessas poucas linhas de código.
O detalhe para obter os valores dos indicadores do usuário e técnicos, assim como da série temporal no MQL5, é que isso é feito copiando os dados para os arranjos variáveis dos usuários. Isso pode levar a acúmulo de dados completamente desnecessário, para as contas atuais.
A forma mais fácil de verificar tudo isso é um recebimento de dados específico de algum indicador técnico. Como um exemplo, podemos tomar mover o iAMA e construir, na base do indicador técnico, um indicador personalizado AMkA.
Para copiar os dados utilizaremos a primeira versão da chamada da função CopyBuffer (), com um pedido para a posição de início e o número de elementos necessários para copiar. No indicador AMkA, a incrementação móvel da barra atual é processada utilizando um desvio padrão do indicador técnico e, então, para obter os sinais de negociação, esse incremento é comparado com o valor de desvio padrão processado total.
Portanto, no caso mais simples para implementação do indicador AMkA, você deveria primeiro criar um indicador, no qual o buffer do indicador contém o valor da incrementação móvel (indicador dAMA). E, então, em outro indicador, utilizando o nome do indicador com incrementos AMA, obtemos o valor resultante, processado pelo indicador de desvio padrão.
O processo de criação de indicadores análogos já tem sido examinado em detalhe em vários artigos nesses tópicos, então não pararei nisso, e analisarei apenas os detalhes de acessar os buffers do indicador chamado, no código de outro indicador.
No vasto recurso da internet, já observamos o surgimento de exemplos do MQL5, nos quais os seus autores literalmente copiam os conteúdos completos de buffers de indicadores em arranjos intermediários dinâmicos. E após isso, todos os valores, um a um, são transferidos para os buffers do indicador finais desses arranjos intermediários, utilizando o operador do ciclo.
Para resolver o nosso problema, essa abordagem parece maximamente simples
if (CopyBuffer(AMA_Handle, 0, 0, rates_total, Array) <= 0) return(0); ArrayCopy(AMA_Buffer, Array, 0, 0, WHOLE_ARRAY);
(indicador dAMA !!!!!!. mq5) Ou assim
if (CopyBuffer(AMA_Handle, 0, 0, rates_total, Array) <= 0) return(0); for(bar = 0; bar < rates_total; bar++) { AMA_Buffer[bar] = Array[bar]; /* here is the code of indicator calculations */ }
Mas qual é o preço para tal solução despretensiosa? Primeiramente, seria melhor ganhar um pouco de entendimento do que seria o curso de ação maximamente racional. Primeiro de tudo, não há razão a sério justificada para utilizar o arranjo intermediário Array [], e os dados devem ser copiados diretamente para o buffer do indicador AMA [].
Segundo, em cada ponto do indicador é necessário copiar os valores em apenas três casos:
- das barras que surgiram recentemente
- das barras fechadas,
- das barras abertas no momento.
Os valores remanescentes no buffer do indicador existem, e não há porque a escrita múltipla deles.
//--- calculation of the required number of copied data int to_copy; if(prev_calculated > rates_total || prev_calculated <= 0)// verification for the first start of indicator calculation to_copy = rates_total - begin; // calculated number of all bars else to_copy = rates_total - prev_calculated + 1; // calculated number of only new bars //--- copy the reappearing data into the indicator buffer AMA_Buffer[] if (CopyBuffer(AMA_Handle, 0, 0, to_copy, AMA_Buffer) <= 0) return(0);
é muito natural que o código final nesse caso final de alguma forma seja mais complexo (indicador dAMA.mq5), mas agora podemos utilizar a metodologia, proposta por mim no início desse artigo, para conduzir testes em ambos os casos, e tirar as conclusões apropriadas. Dessa vez, vamos aumentar o período de teste para um ano.
Finalmente, após passar no teste no diário de estratégia do verificador, obtemos o tempo necessário de teste do consultor especialista dAMA_Test
O tempo resultante de passar no teste em 43,937 ms está dentro de uma faixa razoável. O que, infelizmente, não pode ser dito sobre o tempo resultante análogo de passar o teste com o consultor especialista dAMA !!!!!!_ Test
O tempo de passar no teste é 960 625 ms, que é vinte vezes mais longo do que o caso anterior. A conclusão parece bem óbvia. O código deve ser escrito na forma mais econômica, de forma que não realizará nenhum cálculo desnecessário!
O indicador AMkA, construído nos princípios acima, não demonstra nada novo, então pararei apenas nos detalhe de copiar os dados nesse caso.
//---- declaring local arrays double dAMA_Array[], StdDev_Array[]; //---- indexation of elements in arrays just like in time series ArraySetAsSeries(dAMA_Array, true); ArraySetAsSeries(StdDev_Array, true); //--- calculation of the number of copied data int to_copy; if(prev_calculated > rates_total || prev_calculated <= 0)// verification of the first start of indicator calculation to_copy = rates_total - begin; // calculated number of all bars else to_copy = rates_total - prev_calculated + 1; // calculated number of only new bars //--- copy the newly appeared data into the indicator buffer and local dynamic arrays if(CopyBuffer(dAMAHandle, 1, 0, to_copy, AMABuffer ) <= 0) return(0); if(CopyBuffer(dAMAHandle, 0, 0, to_copy, dAMA_Array ) <= 0) return(0); if(CopyBuffer(StdDevHandle, 0, 0, to_copy, StdDev_Array) <= 0) return(0);
Tudo é feito de uma forma completamente análoga, exceto que agora os dados são copiados em um único buffer de indicador e dois arranjos declarados localmente para cálculos intermediários.
A implementação de todos os cálculos do indicador dentro do indicador, como uma das formas para otimização
Tudo isso é muito interessante mas tal estrutura complexa de chamadas sucessivas do usuário e indicadores técnicos parece um pouco suspeita. E deve ser medida de alguma forma. Mas para fazer isso, não doeria possuir o código do indicador AMkA, que seria localizado dentro do indicador do usuário, e não usaria as chamadas para outros indicadores.
Para um programador que entendeu completamente o processo de escrever os indicadores no MQL5, esse problema não exige muito esforço. Primeiramente o código do indicador do usuário AMA.mq5 está escritos e então os elementos necessários do código são adicionados a ele para a implementação do indicador AMkA_.mq5. Recebemos outro segundo ciclo grande do cálculo do indicador, no qual o arranjo do usuário é carregado com o incremento do indicador AMA,
//---- the main cycle of calculating the AMkA indicator for(bar = first; bar < rates_total; bar++) { //---- load the increments of the AMA indicator into the array for intermediate calculations for(iii = 0; iii < ama_period; iii++) dAMA[iii] = AMABuffer[bar - iii - 0] - AMABuffer[bar - iii - 1];
então esse arranjo é utilizado para realizar operações, análogas ao cálculo do indicador técnico StDev baseado nos dados do indicador dAMA.mq5.
//---- find the simple average of increments of AMA Sum = 0.0; for(iii = 0; iii < ama_period; iii++) Sum += dAMA[iii]; SMAdif = Sum / ama_period; //---- find the sum of the square differences of increments and the average Sum = 0.0; for(iii = 0; iii < ama_period; iii++) Sum += MathPow(dAMA[iii] - SMAdif, 2); //---- determine the final value of the meansquare deviation of StDev from the increment of AMA StDev = MathSqrt(Sum / ama_period);
O resto do código é absolutamente análogo ao código do indicador AMkA.mq5, e não representa interesse especial para nós. Agora podemos começar a testar os indicadores AMkA_.mq5 e AMkA.mq5 com a ajuda dos Consultores Especialistas AMkA__Test.mq5 e AMkA_Test.mq5.
Não há dificuldades ascendentes com o teste do indicador AMkA_.mq5, e o tempo de teste está bem dentro do cronograma aceitável
Mas o indicador AMkA.mq5 foi incrivelmente lento
O resultado dele acabou por ser tanto quanto sete vezes pior do que seus "irmãos". Que outros comentários podem existir? A conclusão é bem óbvia: que construir tais construções complexas, consistindo de várias chamadas consecutivas de indicadores, um do outro, não é muito prudente e adequado apenas para testes preliminares!
Obviamente, esses resultados foram obtidos em uma versão de teste do terminal do cliente, e é muito difícil dizer agora como ele parecerá no futuro. Mas em relação ao campeonato próximo de robôs de negociação, pode ser argumentado bem claramente que isso é um tópico relevante e eficiente.
Algumas características de chamar indicadores dos consultores especialistas
Tudo que está relacionado a otimização do acesso aos dados do usuário e indicadores técnicos no código de programação dos indicadores, é igualmente aplicável à otimização do acesso aos dados do usuário e indicadores técnicos no código do programa do consultor especialista. Além das situações já apresentadas, o código do consultor especialista possui um outro fator, que pode significativamente influenciar o teste e otimização dos sistemas de negociação.
Um grande número de consultores especialistas normalmente processam os dados indicadores apenas no momento da mudança da barra e, por essa razão, nesses consultores especialistas não há necessidade de puxar a função CopyBuffer () para cada ponto.
Nessa situação, há uma necessidade para os consultores especialistas de conseguir os dados dos buffers do indicador apenas durante a mudança de barra. Assim, as chamadas dos indicadores deveriam ser localizadas no bloco atrás dos colchetes, o acesso ao qual é permitido apenas uma vez para cada mudança da barra, se todos os dados necessários do buffer do indicador são copiados com sucesso nos arranjos para cálculos intermediários.
A melhor coisa que serve como tal filtro é uma função do usuário, que retorna uma unidade lógica ao tempo em que havia ma mudança na barra atual. O arquivo IsNewBar.mqh contém a minha versão, relativamente universal, dessa função:
bool IsNewBar ( int Number, // Number of call to the function IsNewBar in the program code of the expert Advisor string symbol, // The chart symbol on which the data calculation is done ENUM_TIMEFRAMES timeframe // Timeframe of the chart on which the data calculation is done )
Quando utilizando essa função no código do consultor especialista, ele pode se parecer com isso:
//---- declaration of a static variable - array for storing the values of the AMA indicator static double AMA_Array[3]; //---- calling up the AMA indicator for copying its values into the AMA_Array array if (IsNewBar(0, Symbol(), 0)) { CopyBuffer(AMA_Handle, 0, 1, 3, AMA_Array); }
Mas é muito mais racional agir diferentemente nesse caso. O fato é que quando você chama a CopyBuffer (), os dados no arranjo AMA_Array [] podem não ser copiados e, em tal caso, você pode precisar chamar essa função para cada ponto até haver uma opção bem sucedida para copiar os dados, que é implementada por uma certa complicação do filtro
//---- declaration of a static variable - array for storing values of the AMA indicator static double AMA_Array[3]; //---- declaration of the static variable for storing the results of copying the data from the AMA indicator static bool Recount; //---- calling up the AMA indicator for copying its values into the AMA_Array array if (IsNewBar(0, Symbol(), 0) || Recount) { if (CopyBuffer(AMA_Handle, 0, 1, 3, AMA_Array) < 0) { Recount = true; // attempt of data copying was unsuccessful return; // exit the function OnTick() } //---- All operations of copying from the indicator buffers are successfully completed // there is no need for returning to this block until the next bar change Recount = false; }
Agora que os detalhes da chamada racional da função de cópia dos valores do indicador no código do Consultor Especialista estão claros, você pode testar os benefícios de aplicar a função IsNewBar () nos consultores especialistas.
Então possuímos duas opções de Consultores Especialistas, disponíveis para serem testadas no verificador de estratégia, o primeiro - AMA_Test.ex5. Ele copia os dados do buffer do indicador a cada ponto.
O segundo - IsNewBar_AMA_Test.mq5 copia os dados apenas durante a mudança da barra.
Sim! Os resultados do teste são de alguma forma decepcionantes. Acontece que chamar uma função IsNewBar () em cada ponto é muito mais caro do que copiar os dados em três células do arranjo do usuário!
Aqui eu gostaria de chamar a sua atenção para outra parte do indicador importante, mas aparentemente imperceptível. O fato é que se obtermos o nome do indicador na função OnInit (), então independente de se copiamos ou não os dados desse indicador dentro da função OnTick (), seus cálculos em barras ainda não calculadas e atuais, ainda serão feitos em cada ponto.
Então, se o novo Consultor Especialista não necessita dos valores dos indicadores contados da barra aberta atual, é melhor, em termos de poupar tempo, desativar o cálculo desses valores. Isso é feito facilmente - reduza por um a borda direita do ciclo principal da recontagem da barra no indicador antes das mudanças, esse ciclo no indicador AMA.mq5 se parecia com isso
//---- main cycle of indicator calculation for(bar = first; bar < rates_total; bar++)
E após a mudança, ele se parecerá com isso
//---- main cycle of indicator calculation for(bar = first; bar < rates_total - 1; bar++)
Indicador AMA_Ex.mq5. Agora você pode testar esse indicador (consultor especialista AMA_Ex_Test.mq5)
Claro, esse resultado é melhor do que o teste do indicador AMA por 21%, que não é tão ruim, mas se pensamos sobre isso, esse resultado poderia ser muito melhor.
Conclusão
Finalmente, a eficiência do código de um programa é um parâmetro relativamente objetivo. A eficiência pode ser medida, logicamente analisada e em certas situações, significativamente aumentada. Os métodos pelos quais isso é feito não são muito complicados. Tudo que é necessário é um pouco de paciência, e fazer mais do que aquelas práticas que diretamente afetam o lucro do sistema de negociação automatizado.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/109
![O uso de ORDER_MAGIC para negociação com diferentes consultores especialistas em um único instrumento](https://c.mql5.com/2/0/order_magic_MQL5__1.png)
![Testando o desempenho do cálculo das médias móveis no MQL5](https://c.mql5.com/2/0/moving_averages_performance_MQL5__1.png)
![Uma biblioteca para construção de um gráfico pelo Google Chart API](https://c.mql5.com/2/0/Google_Chart_API_MQL5__1.png)
![Criando um Consultor Especialista, que negocia em um número de instrumentos](https://c.mql5.com/2/0/multi_assets_EA_MQL5__1.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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