O preço da lata != preço ? - página 5

 

Comecei com esta premissa básica sobre a igualdade de preços (e não apenas a igualdade das duplas) -

(P1) Assumindo y = 1.50000: x == y, desde que x seja qualquer número real que seja (i) maior ou igual a 1.499995 e (ii) menor que 1.500005.

Construindo sobre P1, concluí que -

(P2) Assumindo y = 1.50000: a == y, b == y, e a == b, desde que a e b sejam números reais que sejam (i) maiores ou iguais a 1.499995 e (ii) menores que 1.500005.

Exemplos incluem: 1.500055 == 1.50006, 1.500055 == 1.500064, 1.500051 != 1.500059, e 1.500054 != 1.500056.

Usando o acima, criei uma função (abaixo) que (1) toma dois preços como argumentos, (2) arredonda esses preços para o ponto equivalente mais próximo, e (3) determina se esses dois preços são iguais.

bool IsEqual(double price1, double price2) {
   // Price Conditioning
   //    * this fixes the occurrence of 1.5000551 != 1.5000550
   price1 += Point * 0.0015;
   price2 += Point * 0.0015;
      
   int p1 = MathRound(price1 / Point),
       p2 = MathRound(price2 / Point);
          
   return (p1 == p2);
}

Esta função é simples e direta, mas eu deveria comentar um pouco sobre a parte "Condicionamento de Preços". Como muitos de nós sabemos, duplica (ou seja Descobri que quando arredondei 1.5000551 e 1.5000550 para o ponto mais próximo e comparei o resultado (1.50006 e 1.50005, respectivamente), eles pareciam não ser iguais mesmo que, sob P1 e P2 acima, eles deveriam ser iguais. Concluí (depois de conduzir alguns testes) que o literal 1.5000550 foi armazenado na variável como ~1.5000549999. Para remediar isto, decidi que se o preço estivesse dentro de 15 milésimos de um ponto a partir da metade (x.xxxxx5), eu assumiria que o preço atingiu o limite mínimo para arredondamento até o ponto mais próximo. Assim, acrescento 15 dez milésimos de ponto a cada preço antes de arredondar para o ponto mais próximo. Neste momento, não acredito que esta adição tenha consequências involuntárias. Além disso, estes valores podem ser ajustados para aumentar/diminuir a suposição de arredondamento para o ponto mais próximo.

RaptorUK e WHRoeder(e outros):Usando o acima descrito como um plano, construí a seguinte função chamada ComparePrices() que é baseada no posto anterior do RaptorUK:

#define EQ      1
#define NEQ     2
#define LT      3
#define LToE    4
#define GT      5
#define GToE    6

bool ComparePrices(double FristPrice, double SecondPrice, int ComparisonType) {
   // Price Conditioning
   FirstPrice  += Point * 0.0015;
   SecondPrice += Point * 0.0015;
      
   int price1 = MathRound(FirstPrice / Point),
       price2 = MathRound(SecondPrice / Point);
                
   switch(ComparisonType) {
      case LToE: return (price1 < price2 || price1 == price2);
      case GToE: return (price1 > price2 || price1 == price2);
      case LT:   return (price1 < price2);
      case GT:   return (price1 > price2);
      case EQ:   return (price1 == price2);
      case NEQ:  return (price1 != price2);
      default:   return (false);
   }    
}

Como sempre, comentários instrutivos/construtivos são bem-vindos. :)

 

Eu mesmo tive uma pequena brincadeira com isso - tentando alcançar um compromisso aceitável de legibilidade e desempenho.


Eu me conformei com as funções individuais eq(a,b), ne(a,b), lt(a,b) etc...


Ex:

if (eq(a,b)) { ...}


Com relação ao desempenho da minha lenta VM para 4999999 iterações, obtive as seguintes medidas de base:

Loop vazio : 370ms

inline MathAbs(a-b) < gHalfPoint (global) : 2482ms

Função Bool Vazio: 4266ms <... tenho o objetivo de chegar o mais próximo possível desta figura.

As implementações mais rápidas de eq() que gerenciei estão abaixo.

Eles são cerca de 2,3 vezes mais lentos do que uma chamada de MathsAbs() em linha e 1,3 vezes mais lentos do que uma chamada de função booleana vazia .que apenas retorna verdadeiro.

Também como um aparte descobri que a MQL não faz curto-circuito com expressões booleanas.

bool eq(double a,double b) {

   if (a > b) {
      return ((a-b) < gpoint2);
   } else {
      return ((b-a) < gpoint2);
   }

}

em 5558ms

Ou se preferir estática a global (para manter todos os códigos em um só lugar):

bool eq(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a > b) {
      return (a-b < p2);
   } else {
      return (b-a < p2);
   }
}

em 5718ms


lt(), gt() etc. devem ser mais rápidos, pois eq() e ne() são mais complicados.

 
RaptorUK: Então, como faço para que o TestValue seja igual a 1,57373 não > ou < ?

O ponto flutuante NUNCA é exato para alguns números.

https://en.wikipedia.org/wiki/Floating_point

Os números de ponto flutuante são números racionais porque podem ser representados como um número inteiro dividido por outro. Por exemplo, 1,45×103 é (145/100)*1000 ou 145000/100. A base, entretanto, determina as frações que podem ser representadas. Por exemplo, 1/5 não pode ser representado exatamente como um número de ponto flutuante usando uma base binária, mas pode ser representado exatamente usando uma base decimal (0,2, ou 2×10-1). Entretanto, 1/3 não pode ser representado exatamente por binário (0,010101...) nem decimal (0,333....), mas na base 3 é trivial (0,1 ou 1×3-1) .
É por isso que eu digo NUNCA, NUNCA use NormalizeDuplo. É um Kludge. Seu uso é SEMPRE errado.
 
Thirteen:

case LToE: return (price1 < price2 || price1 == price2);
case GToE: return (price1 > price2 || price1 == price2);

O valor duplo do corretor poderia ser de 1,2345750000000000000000 a 1,23458499999999999 e ainda ser considerado o mesmo preço de 1,23458.

No entanto, sua função diz que 1.2345750000000000000000 NÃO é GToE de 1.2345849999999999999

Contudo, sua função diz que 1.23458499999999999999 NÃO é LToE de 1.2345750000000000000000

Você deve usar um ponto/2 nas comparações https://www.mql5.com/en/forum/136997/page3#780837

 
ydrol:

Eu mesmo tive uma pequena brincadeira com isso - tentando alcançar um compromisso aceitável de legibilidade e desempenho.

Acredito que 0,0 é um caso especial para que você possa testar diretamente com 0,0
 
WHRoeder:

O valor duplo do corretor poderia ser de 1,2345750000000000000000 a 1,23458499999999999 e ainda ser considerado o mesmo preço de 1,23458.

Eu concordo em geral. Veja meu P1 e P2 no meu post acima.

WHRoeder:

No entanto, sua função diz que 1.234575000000000000 NÃO é GToE de 1.2345849999999999999

Contudo, sua função diz que 1.23458499999999999999 NÃO é LToE de 1.2345750000000000000000

O problema surge de como o MT4/MQL armazena valores de ponto flutuante em variáveis. Por exemplo, o MT4/MQL é um valor de ponto flutuante:

double p1 = 1.234575000000000000, p2 = 1.23458499999999999;
Print ("p1 = ", DoubleToStr(p1, 8), " p2 = ", DoubleToStr(p2, 8));

imprime as duas variáveis no log/jornal:

Teste de comparação de preços nº 1

Como você pode ver, p2 não é mais 1,23458499999999999, mas, em vez disso, passa a ser 1,23458500--devido, creio eu, a arredondar. Esta é a razão pela qual minha função diz que p1 não é GToE para p2; e como você pode ver no código abaixo, seu código também sugere o mesmo - ou seja, que p1 não é GToE para p2 e que p1 não é Igual a p2.

double p1 = 1.234575000000000000, p2 = 1.23458499999999999;
Print ("p1 = ", DoubleToStr(p1, 8), " p2 = ", DoubleToStr(p2, 8));
Print ("GToE: ", p1 >= p2);
Print ("ComparePrices() for GToE: ", ComparePrices(p1, p2, GToE));
Print ("WHRoeder GToE: ", p1 - p2 > -Point/2.);
Print ("WHRoeder NEQ: ", MathAbs(p1 - p2) > Point / 2.);

Teste de comparação de preços nº 2

Você deve usar um ponto/2 nas comparações

Há uma possibilidade de que o Ponto/2 seja muito pequeno de um desvio máximo. Por exemplo:

double p1 = 1.234575000000000000, p2 = 1.23458499999999999, p3 = 1.234580;
Print ("p1 = ", DoubleToStr(p1, 8), " p2 = ", DoubleToStr(p2, 8), " p3 = ", DoubleToStr(p3, 8));
Print ("#1 WHRoeder NEQ: ", MathAbs(1.234575000000000000 - 1.23458499999999999) > Point / 2.);
Print ("#2 WHRoeder NEQ: ", MathAbs(p1 - p3) > Point / 2.);
Print ("#3 WHRoeder NEQ: ", MathAbs(p2 - p3) > Point / 2.);

Teste de comparação de preços #3

Se a suposição é que 1,234575 é igual a 1,234580, então por que o número 2 mostra o NEQ? Além disso, se assumirmos que 1,23458 é um preço que pode significar um preço do corretor que está em qualquer lugar entre 1,2345750000000000000000 até 1,23458499999999999, por que o nº 1 deve mostrar o NEQ? Eles não deveriam ser iguais se compartilham o mesmo ponto de preço (daí minha Premissa nº 2 no meu posto acima)?

 

@Thirteen,


Em seu código você está olhando para diferenças de arredondamento intencionais devido à lógica de aplicação, não para erros de arredondamento involuntários devido a erros de ponto flutuante, daí a diferença:

Os dois tipos de "arredondamento" são:

a) Erros de arredondamento intrínsecos devido a frações binárias no formato IEEE. - Estes números devem ser exatamente os mesmos, mas não são devidos à representação binária das frações decimais. Eles são arredondados pela representação MQ4 de decimais.

b) Arredondamento explícito para algum número ou casas decimais. (por exemplo, ao imprimir, ou enviar preços para um Corretor). - Estes não são realmente destinados a serem os mesmos valores, em vez disso, estão sendo arredondados pela lógica de aplicação para a conveniência de alguém.

Isto não é realmente um erro. Erros devidos apenas à representação em ponto flutuante dificilmente serão tão grandes (a menos que se calcule mal uma série). Mas você pode querer fazer este tipo de comparação em sua aplicação de acordo com sua própria lógica.


Os erros de arredondamento intrínsecos[a] são geralmente muito pequenos ( ordens de magnitude menor que o Ponto ) e não intencionais. A aplicação não é capaz de arredondar esses números para ser exatamente o valor pretendido, usando o duplo tipo de dados.

As diferenças explícitas de arredondamento[b] são intencionais e muito maiores (+/- 0,5 ponto). (neste caso). portanto, dois números arredondados pela lógica de sua aplicação para o mesmo valor de ponto podem ser quase um ponto inteiro à parte originalmente.


O ideal seria primeiro arredondar os números [b] (somente se for necessário arredondar) e depois compará-los [a] em que ponto o erro é muito pequeno devido a limitações de duplo. (por exemplo, < 0,0000001)

Mas seu código é para comparar antes de arredondá-los, caso em que você tem que detalhar com as diferenças muito maiores possíveis. No entanto, o arredondamento nem sempre é necessário. Eu só o utilizaria quando enviasse os preços ao corretor.


Pense nisso de outra forma (se o MQ4 tivesse usado o código decimal binário - que permite a representação exata das frações decimais - então todas as questões relativas ao Preço != Preço desapareceriam,

mas você ainda teria que arredondar e comparar números em sua aplicação para o ponto mais próximo para certas operações. (Principalmente, funçÕes de PedidoXXX)


>> "se assumirmos que 1,23458 é um preço que pode significar um preço do corretor que vai de 1,2345750000000000000000 a 1,23458499999999999".

Eu poderia estar errado aqui (não tenho certeza de como os corretores trabalham), mas acho que um preço do corretor de 1,23458 é exatamente isso. especialmente com lotes de 100.000 dólares e maiores para considerar. Caso contrário, muito dinheiro a ser feito (pelo corretor) explorando a diferença nos preços publicados.

Meu entendimento é que na verdade é somente quando se envia para o corretor que você tem que arredondar, não durante toda a sua aplicação. Nesse caso, as comparações para pequenos erros devem ser suficientes.

A imprecisão do ponto flutuante é separada do arredondamento dos preços do corretor. Mas se você quiser lidar com os dois ao mesmo tempo, acho que isso é preferência pessoal (pode ficar confuso?).

 

Aqui está minha versão completa, (espero que sem bugs).

Isto oferece 6 funções:

eq(a,b) =
ne(a,b) !=
gt(a,b) >
lt(a,b) <
ge(a,b) >=
le(a,b) <=

if (ge(Bid,target)) sell sell sell...


O racional é manter o código legível (IMO), e reduzir a probabilidade de erros de digitação, sem que haja muita perda de desempenho.

Para todos os efeitos, estas funções devem ser tão rápidas quanto possível, utilizando as funções de usuário do MQ4,

(para desempenho vs MathAbs(a-b) < HalfPoint ver https://www.mql5.com/en/forum/136997/page5#822505 embora em um EA real (em oposição a um benchmark) eu suspeito que a diferença é insignificante.


bool gt(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a < b) {
      return (false);
   } else {
      return (a-b > p2);
   }
}
bool lt(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a > b) {
      return (false);
   } else {
      return (b-a > p2);
   }
}
bool ge(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a >= b) {
      return (true);
   } else {
      return (b-a <= p2);
   }
}
bool le(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a <= b) {
      return (true);
   } else {
      return (a-b <= p2);
   }
}
bool eq(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a > b) {
      return (a-b <= p2);
   } else {
      return (b-a <= p2);
   }
}

bool ne(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a > b) {
      return ((a-b) > p2);
   } else {
      return ((b-a) > p2);
   }
}
 
ydrol:

Aqui está minha versão completa, (espero que sem bugs).

...

bool eq(double a,double b) {
   static double p2=0;
   if (p2==0) p2 = Point/2;
   
   if (a > b) {
      return (a-b <= p2);
   } else {
      return (b-a <= p2);
   }
}

A premissa frequentemente citada é:

  • O valor duplo do corretor poderia ser de 1,2345750000000000000000 a 1,23458499999999999 e ainda ser considerado o mesmo preço de 1,23458.

Considerando esta premissa e usando seu código como pano de fundo, você poderia me explicar por que você está dizendo (a) 1.234576 e 1.234584 não são considerados iguais, (b) 1.234577 e 1.234583 não são considerados iguais, mas (c) 1.234578 e 1.234582 são considerados iguais? Por que (e como) o exemplo (b) é menos igual do que o exemplo (c)?

Como afirmei acima, considero todos esses preços iguais porque cada ação tem o mesmo preço - nomadamente, 1,23458. Este exemplo ilustra porque acredito (e tenho dito acima) que o Ponto/2 pode ser muito pequeno de um desvio máximo.

 

@Thirteen, minha resposta às suas observações continua a ser a mesma 3 posts acima do link https://www.mql5.com/en/forum/136997/page5#822672. Vou repetir a parte que pode levar ao momento de luz-bolbo na compreensão do meu ponto de vista: (com um pouco de revisão e ênfase acrescentada)

Think of it another way (If MQ4 had used Binary Coded Decimal - which allows exact representation of Decimal fractions - then most of the original issues regarding Price != Price would go away, (and is often used on financial platforms for that very reason )

mas você ainda teria que arredondar e comparar números em sua aplicação para o ponto mais próximo para certas operações. (Principalmente, funxões OrderXXX)


Depende apenas de como você escreve seu código, e se você quiser diferenciar entre arredondamento de aplicações (onde dois números diferentes são conceitual/logicamente tratados como o mesmo para simplicidade/conveniência),

e erros de ponto flutuante. Não há certo e errado, mas acho que uma abordagem mais confusa do que a outra....


Além disso, sou pessoalmente um pouco cético em relação à premissa não citada (mas aberta à correção!), novamente mencionada no post anterior.