Trabalhando Com Doubles no MQL4

MetaQuotes | 6 outubro, 2015

Introdução

A programação MQL abre novas oportunidades para a negociação automática, muitas pessoas em todo o mundo já estão aproveitando isto.

Quando estamos escrevendo um Expert Advisor para negociação, devemos ter certeza de que ele vai trabalhar corretamente.

Muitos novatos às vezes têm algumas perguntas quando ocorrem resultados de alguns cálculos matemáticos diferentes do esperado. O programa é compilado e pode funcionar, mas não como deveria. Então eles verificam o código de novo e de novo, encontrando novos "erros" na linguagem, na implementação, em funções, etc.

Na maioria dos casos, uma análise cuidadosa mostra que a linguagem mql4 e o compilador estão trabalhando corretamente, porém o código é que tem um pequeno erro e isto pode levar um longo tempo para encontrar o problema e corrigir.

Neste artigo vamos considerar erros típicos de programação que ocorrem durante o trabalho com números tipo double nos programas MQL4.


1. Verificando os valores numéricos

Para depurar e verificar os resultados do cálculo você pode usar a função DoubleToStrMorePrecision (número tipo double, int precision) da biblioteca padrão stdlib.mq4, permitindo o controle dos valores numéricos dos números tipo double da precisão especificada.

Esta função permitirá poupar tempo na procura de possíveis erros.

Um exemplo:

#include <stdlib.mqh>
int start()
  {
   double a=2.0/3;
   Alert("Standard output:",a,", 8 digits precision:",DoubleToStr(a,8),", 15 digits precision:", DoubleToStrMorePrecision(a,15));
   return(0);
  }

Resultado:

A saída padrão:0.6667, 8 dígitos de precisão:0.66666667, 15 dígitos de precisão:0.666666666666667

Em alguns casos, para mostrar os valores dos números tipo double (por exemplo, em Print, Alert, Comment) é melhor usar as funções DoubleToStr e DoubleToStrMorePrecision (a partir de stdlib.mq4) para apresentar valores mais precisos, em vez do padrão da saída de 4 dígitos de precisão.

Por exemplo:

#include <stdlib.mqh>
int start()
  {
   double a=2.0/100000;
   Alert("Standard output=",a,", More precise output=",DoubleToStrMorePrecision(a,15));
   return(0);
  }

retorna: "Standard output=0, More precise output=0.000020000000000".


2. Exatidão da precisão dos dígitos decimais

Por causa do formato de ponto flutuante da precisão double, existe uma precisão limitada do seu armazenamento.

Por exemplo, se assumirmos que temos uma precisão ilimitada, como na teoria, para qualquer número double A e B, as seguintes expressões são sempre válidas:

(A/B)*(B)=A,

A-(A/B)*B=0,

(A/B)*(B/A)=1 etc.

A precisão do armazenamento de dígitos decimais em computador é dependente do tamanho da fração e limitada a 52 bits. Para ilustrar esse fato, vamos considerar o seguinte exemplo.

No primeiro ciclo de (i) que estão calculando o fatorial de 23 (produto dos números inteiros de 2 a 23), o resultado é: 23! =25852016738884976640000. O resultado é armazenado na variável a do tipo double.

No ciclo seguinte (j), que está dividindo o valor do resultado a por todos os inteiros de 23 para 2. Então podemos esperar que a=1.

#include <stdlib.mqh>
int start()
  {
   int maxfact=23;
   double a=1;
   for (int i=2; i<=maxfact; i++) { a=a*i; }
   for (int j=maxfact; j>=2; j--) { a=a/j; }
   Alert(" a=",DoubleToStrMorePrecision(a,16));
   return(0);
  }

Em vez disso, temos:

a=1.0000000000000002

Como vemos nós temos um IMPRECISÃO em 16 dígitos.

Se aumentarmos o cálculo para 35!, vamos chegar a=0,9999999999999998.

A linguagem MQL tem uma função NormalizeDouble, que permite arredondar o número double para a precisão especificada.

As constantes do tipo double são armazenadas na memória de um modo semelhante às variáveis double, por conseguinte é necessário ter em conta o limite de 15 dígitos significativos na sua definição.

Mas não confunda a exatidão da precisão dos dígitos decimais com a precisão de cálculo dos números tipo doubles.

O alcance dos possíveis valores para os números double é muito amplo: a partir de -1.7*e-308 para 1.7*e308.

Vamos tentar estimar o menor expoente do número double.

int start()
  {
  double R=1;
  int minpwr=0;
  while (R>0) {R=R/10; minpwr--;}
  Alert(minpwr);
  return(0);
  }

O programa irá imprimir -324, mas temos de ter em conta os dígitos decimais na fração (+15) e nós entraremos num valor aproximado ao expoente do menor número double.


3. Função NormalizeDouble

A função NormalizeDouble (valor double, int dígitos) arredonda o valor do ponto flutuante com a precisão dada. Retorna valor normalizado do tipo double.

Por exemplo:

int start()
  {
   double a=3.141592653589;
   Alert("a=",DoubleToStr(NormalizeDouble(a,5),8));
   return(0);
  }

o resultado é:

a=3.14159000

Observe que nas operações de negociação é impossível usar preços não normalizados, cuja exatidão exceda pelo menos por um dígito, assim exigido por um servidor de negociação.
O StopLoss, TakeProfit e o valores de Preços para as ordens pendentes devem ser normalizados com a precisão, o valor é armazenado na variável predeterminada Digits.


4. Verificar a existência de igualdade de dois números double

Recomenda-se comparar dois números double usando a função CompareDoubles (double number1,double number2) da biblioteca stdlib.mq4:

//+------------------------------------------------------------------+
//| comparação correta de 2 doubles                                  |
//+------------------------------------------------------------------+
bool CompareDoubles(double number1,double number2)
  {
   if(NormalizeDouble(number1-number2,8)==0) return(true);
   else return(false);
  }

Essa função compara number1 e number2 do tipo double com uma precisão de até 8 dígitos decimais.

O exemplo:

#include <stdlib.mqh>
int start()
  {double a=0.123456781;
   double b=0.123456782; 
   if (CompareDoubles(a,b)) {Alert("They are equal");}
   else {Alert("They are different");}
  }

irá sair:

Eles são iguais

porque são diferente apenas no nono dígito.

Se necessário, você pode escrever sua própria função de comparação (com a precisão desejada) de uma forma similar.


5. Dividindo "integers"

É necessário lembrar que se estamos dividindo dois inteiros, então vamos obter um número inteiro como resultado.

Eis o código:

int start()
  {
   Alert(70/100);
   return(0);
  }

saída será 0, porque 70 e 100 são números inteiros.

Assim como na linguagem C/C++, na MQL o resultado da divisão de dois inteiros será um número inteiro, neste caso é 0.

No entanto, se o numerador ou denominador é o dobro (ou seja, tem uma parte fracionária), o resultado será do tipo double. Portanto Alert (70/100,0); vai apresentar o valor 0,7 como correto.

Por exemplo:

int start()
  { double a=1/3;
    double b=1.0/3;
   Alert("a=",a,", b=",b);
   return(0);
  }

irá sair "a=0, b=0.3333"


6. Conversão para números "integer" e "double"

Vamos considerar o código:

double xbaseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + (xBid - xbaseBid)/xPoint;
Alert(i);

Nós obteremos 100, mas parece que ele deveria ser 101, por causa da equidade evidente: 0,0001/0,0001=1

O mesmo exemplo em C/C++:
double baseBid=1.2972,Bid=1.2973,Point=0.0001;
int i = 100 + (Bid - baseBid)/Point;
printf("%d\n",i);

também dá 100.

Para determinar a razão desse fato, vamos considerar o código:

double a=0.99999999999999;
int i = 100 + a;
Alert(i);

que dá i=100.

No entanto, se eu executar alguma melhoria da precisão para um:

double a=0.999999999999999;
int i = 100 + a;
Alert(i);

então vamos obter 101.

A razão desse fato é que números inteiros e duplos são complicados para baixos valores de precisão.

Portanto nas operações deste tipo é recomendado realizar o arredondamento das expressões semelhantes usando a função MathRound (valor double)

que retorna o valor double arredondado para o número inteiro mais próximo:

double baseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + MathRound((xBid - baseBid)/xPoint);
Alert(i);

Nesse caso, vamos obter o valor correto 101.

Existe um erro comum no uso da função OrderModify, especialmente para a programação do Trailing Stop. A função OrderModify retorna um erro № 1: ERR_NO_RESULT no caso dos novos valores para as ordens serem as mesmas já definidas. Por este motivo é necessário a realização de uma verificação cuidadosa para a igualdade (usar NormalizeDouble) e de pontos nos cálculos de tamanho.

Recordamos que o terminal de cliente permite mudar os parâmetros da ordem apenas se os novos valores são diferentes dos já definidos, pelo menos em 1 ponto.


7. Características da função MathMod

Em MQL, a função MathMod (double v1 double v2) corresponde completamente à função fmod (double v1, v2 duplo) de MSVC6, devido a chamada direta à função "fmod", a partir do uso da biblioteca da C Runtime.

Em alguns casos, a função fmod de MSVC6 (e também MathMod) fornece resultados errados.

Se você estiver usando a função MathMod em seus programas, por favor substitua-a para a função a seguir:

double MathModCorrect(double a, double b)
{ int tmpres=a/b;
return(a-tmpres*b);
}

Note que esta observação é somente para MQL4, MQL5 calcula esta função pela sua definição matemática.


Conclusão

Note que a lista não é exaustiva, se você encontrou algo novo que não tenha sido descrito aqui, coloque nos comentários.

Se você ainda não encontrou uma solução, descreva o problema nos comentários, adicione o seu código e profissionais MQL da comunidade irão ajudá-lo.