Can price != price ? - page 5

 

J'ai commencé avec cette prémisse de base sur l'égalité des prix (plutôt que l'égalité des doubles) - (P1).

(P1) Supposons que y = 1.50000 : x == y, tant que x est un nombre réel quelconque qui est (i) supérieur ou égal à 1.499995 et (ii) inférieur à 1.500005.

En m'appuyant sur P1, j'ai conclu que -

(P2). en supposant que y = 1.50000 : a == y, b == y, et a == b, tant que a et b sont des nombres réels qui sont (i) supérieurs ou égaux à 1.499995 et (ii) inférieurs à 1.500005.

Voici quelques exemples : 1,500055 == 1,50006, 1,500055 == 1,500064, 1,500051 != 1,500059, et 1,500054 != 1,500056.

En utilisant ce qui précède, j'ai créé une fonction (ci-dessous) qui (1) prend deux prix comme arguments, (2) arrondit ces prix au point équivalent le plus proche, et (3) détermine si ces deux prix sont égaux.

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);
}

Cette fonction est simple et directe, mais je dois faire quelques commentaires sur la partie " Conditionnement des prix ". Comme beaucoup d'entre nous le savent, les doubles (c'est-à-dire les J 'ai constaté que lorsque j'arrondissais 1.5000551 et 1.5000550 au point le plus proche et que je comparais les résultats (1.50006 et 1.50005, respectivement), ils semblaient ne pas être égaux alors que, selon P1 et P2 ci-dessus, ils devraient l'être. J'ai conclu (après avoir effectué quelques tests) que la valeur littérale 1.5000550 était stockée dans la variable sous la forme ~1.5000549999. Pour remédier à cela, j'ai décidé que si le prix était à moins de 15 dix-millièmes de point du point médian (x.xxxxx5), je supposerais que le prix a atteint le seuil minimum pour arrondir au point le plus proche. En conséquence, j'ajoute 15 dix-millièmes de point à chaque prix avant d'arrondir au point le plus proche. Pour le moment, je ne pense pas que cet ajout ait des conséquences involontaires. De plus, ces valeurs peuvent être ajustées pour augmenter/supprimer l'hypothèse d'arrondi au point le plus proche.

RaptorUK et WHRoeder(et autres) :En utilisant ce qui précède comme un plan, j'ai construit la fonction suivante appelée ComparePrices() qui est basée sur le post précédent de 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);
   }    
}

Comme toujours, les commentaires instructifs/constructifs sont les bienvenus. :)

 

Je me suis un peu amusé avec ça moi-même - en essayant d'atteindre un compromis acceptable entre lisibilité et performance.


J'ai opté pour les fonctions individuelles eq(a,b), ne(a,b), lt(a,b) etc...


Par exemple

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


En ce qui concerne les performances sur ma VM lente pour 4999999 itérations, j'obtiens les mesures de base suivantes :

Boucle vide : 370ms

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

Fonction bool vide : 4266ms <-- Je cherche à me rapprocher le plus possible de ce chiffre.

Les implémentations les plus rapides de eq() que j'ai gérées sont ci-dessous.

Elles sont environ 2,3 fois plus lentes que l'appel à MathsAbs() en ligne et 1,3 fois plus lentes qu'un appel à une fonction booléenne vide qui renvoie juste true.

En passant, j'ai découvert que MQL ne court-circuite pas les expressions booléennes.

bool eq(double a,double b) {

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

}

en 5558ms

Ou si vous préférez les statiques aux globales (pour garder tout le code à un seul endroit) :

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);
   }
}

dans 5718ms


lt(), gt() etc devraient être plus rapides car eq() et ne() sont plus compliqués.

 
RaptorUK: Alors comment faire pour que TestValue soit égal à 1.57373 et non > ou < ?

Vous ne pouvez pas. La virgule flottante n'est JAMAIS exacte pour certains nombres.

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

Les nombres à virgule flottante sont des nombres rationnels car ils peuvent être représentés comme un entier divisé par un autre. Par exemple, 1,45×103 est (145/100)*1000 ou 145000/100. La base détermine toutefois les fractions qui peuvent être représentées. Par exemple, 1/5 ne peut pas être représenté exactement comme un nombre à virgule flottante en utilisant une base binaire mais peut être représenté exactement en utilisant une base décimale (0,2, ou 2×10-1). Cependant, 1/3 ne peut être représenté exactement ni en base binaire (0,010101...) ni en base décimale (0,333....), mais en base 3 , il est trivial (0,1 ou 1×3-1) .
C'est pourquoi je dis de ne JAMAIS, JAMAIS utiliser NormalizeDouble. C'est un gadget. Son utilisation est TOUJOURS erronée.
 
Thirteen:

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

La valeur double du courtier pourrait être n'importe où entre 1,23457500000000 et 1,234584999999999 et être toujours considérée comme le même prix de 1,23458.

Pourtant, votre fonction indique que 1,23457500000000 n'est PAS le GToE de 1,234584999999999.

Pourtant, votre fonction indique que 1,234584999999999999 n'est PAS le LToE de 1,234575000000000000.

Vous devez utiliser un point/2 dans les comparaisons https://www.mql5.com/en/forum/136997/page3#780837.

 
ydrol:

Je me suis un peu amusé avec ça moi-même - en essayant de trouver un compromis acceptable entre la lisibilité et les performances.

Je crois que la version 0.0 est un cas particulier, vous pouvez donc tester directement avec la version 0.0.
 
WHRoeder:

La double valeur du courtier pourrait être n'importe où entre 1,23457500000000 et 1,23458499999999999 et être toujours considérée comme le même prix de 1,23458.

Je suis généralement d'accord. Voir mes P1 et P2 dans mon message ci-dessus.

WHRoeder:

Pourtant, votre fonction indique que 1,234575000000000000 n'est PAS le GToE de 1,234584999999999.

Pourtant, votre fonction indique que 1,234584999999999999 n'est PAS le LToE de 1,234575000000000000.

Le problème vient de la façon dont MT4/MQL stocke les valeurs à virgule flottante dans les variables. Par exemple :

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

imprime les deux variables dans le journal :

Test n°1 de ComparePrices

Comme vous pouvez le voir, p2 n'est plus 1,23458499999999999, mais devient 1,23458500 - en raison, je crois, de l'arrondi. C'est la raison pour laquelle ma fonction dit que p1 n'est pas GToE à p2 ; et comme vous pouvez le voir dans le code ci-dessous, votre code suggère également la même chose - c'est-à-dire que p1 n'est pas GToE à p2 et que p1 n'est pas égal à 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.);

Test n° 2 de ComparePrices

Vous devez utiliser un point/2 dans les comparaisons

Il est possible que Point/2 soit un écart maximal trop petit. Par exemple :

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.);

Test n°3 de ComparePrices

Si l'on suppose que 1,234575 est égal à 1,234580, alors pourquoi le point 2 indique-t-il NEQ ? De plus, si nous supposons que 1,23458 est un prix qui pourrait signifier un prix du courtier compris entre 1,234575000000000000 et 1,234584999999999, pourquoi le numéro 1 affiche-t-il NEQ ? Ne devraient-ils pas être égaux s'ils partagent le même point de prix (d'où ma prémisse n° 2 dans mon message ci-dessus) ?

 

@Thirteen,


Dans votre code, vous recherchez des différences d' arrondi intentionnelles dues à la logique de l'application, et non des erreurs d 'arrondi involontaires dues à des erreurs de virgule flottante, d'où la différence :

Les deux types d'arrondis sont les suivants :

a) Les erreurs d' arrondi intrinsèques dues aux fractions binaires dans le format IEEE. - Ces nombres sont censés être exactement les mêmes mais ne le sont pas en raison de la représentation binaire des fractions décimales. Ils sont arrondis par la représentation MQ4 des décimales.

b) Arrondi explicite à un certain nombre de décimales. (par exemple, lors de l'impression ou de l'envoi de prix à un courtier). - Il ne s'agit pas vraiment des mêmes valeurs, mais elles sont arrondies par la logique de l'application pour la commodité de quelqu'un.

Ce n'est pas vraiment une erreur. Les erreurs dues uniquement à la représentation en virgule flottante ne seront probablement pas aussi importantes (à moins de mal calculer une série). Mais vous pouvez vouloir effectuer ce type de comparaison dans votre application selon votre propre logique.


Les erreurs d'arrondi intrinsèques[a] sont généralement très petites ( ordres de grandeur inférieurs au point ) et involontaires. L'application n'est pas capable d'arrondir ces nombres pour qu'ils aient exactement la valeur voulue, en utilisant le type de données double .

Les différences d'arrondi explicites [b] sont intentionnelles et beaucoup plus grandes (+/- 0,5 point). (dans ce cas). Ainsi, deux nombres arrondis par votre logique d'application à la même valeur peuvent être séparés de presque un point complet à l'origine.


Idéalement, j'arrondirais d'abord les chiffres [b] (uniquement si l'arrondi est nécessaire) , puis je les comparerais [a], l'erreur étant alors très faible en raison des limites du double. (par exemple, < 0,0000001).

Mais votre code est pour comparer avant de les arrondir, dans ce cas vous devez détailler avec les différences possibles beaucoup plus grandes. Cependant, l'arrondi n'est pas toujours nécessaire. Je ne l'utiliserais que pour envoyer les prix au courtier.


Pensez-y d'une autre manière (Si MQ4 avait utilisé le décimal codé binaire - qui permet la représentation exacte des fractions décimales - alors tous les problèmes concernant Prix != Prix disparaîtraient,

mais vous devriez toujours arrondir et comparer les nombres dans votre application au point le plus proche pour certaines opérations. (principalement les fonctions OrderXXX).


>> "si nous supposons que 1,23458 est un prix, cela pourrait signifier un prix du courtier compris entre 1,234575000000000000 et 1,23458499999999999".

Je peux me tromper (je ne sais pas comment fonctionnent les courtiers) mais je pense qu'un prix de 1,23458 de la part du courtier est exactement cela, surtout avec des lots de 100 000 $ et plus à considérer. Autrement, il y a beaucoup d'argent à gagner (par le courtier) en exploitant la différence entre les prix publiés.

Si j'ai bien compris, c'est uniquement lors de l'envoi au courtier que vous devez arrondir, et non dans l'ensemble de votre application. Dans ce cas, des comparaisons pour une petite erreur devraient suffire.

L'imprécision de la virgule flottante est distincte de l'arrondi pour les prix du courtier. Mais si vous voulez traiter les deux en même temps, je suppose que c'est une préférence personnelle (cela pourrait être source de confusion).

 

Voici ma version complète, (en espérant qu'il n'y ait pas de bugs) .

Cela fournit 6 fonctions :

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...


Le rationnel est de garder un code lisible (IMO), et de réduire la probabilité de fautes de frappe, sans trop de perte de performance.

A toutes fins utiles, ces fonctions devraient être aussi rapides qu'il est possible de le faire en utilisant les fonctions utilisateur de MQ4,

(pour les performances par rapport à MathAbs(a-b) < HalfPoint voir https://www.mql5.com/en/forum/136997/page5#822505 bien que dans une vraie EA (par opposition à un benchmark) je soupçonne que la différence est insignifiante.


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:

Voici ma version complète, (en espérant qu'il n'y ait pas de 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);
   }
}

La prémisse souvent citée est la suivante :

  • La double valeur du courtier pourrait être n'importe où entre 1,234575000000000000 et 1,23458499999999999 et être toujours considérée comme le même prix de 1,23458.

En considérant cette prémisse et en utilisant votre code comme toile de fond, pourriez-vous m'expliquer pourquoi vous dites que (a) 1.234576 et 1.234584 sont considérés comme non égaux, (b) 1.234577 et 1.234583 sont considérés comme non égaux, mais (c) 1.234578 et 1.234582 sont considérés comme égaux? Pourquoi (et comment) l'exemple (b) est-il moins égal que l'exemple (c) ?

Comme je l'ai dit plus haut, je considère que tous ces prix sont égaux car ils partagent le même point de prix, à savoir 1,23458. Cet exemple illustre pourquoi je pense (et j'ai dit plus haut) que Point/2 peut être un écart maximal trop petit.

 

@Thirteen, ma réponse à vos observations reste la même 3 posts au-dessus du lien https://www.mql5.com/en/forum/136997/page5#822672. Je vais répéter la partie qui pourrait mener au moment où vous comprendrez mon point de vue : (avec un peu de révision et d'emphase ajoutée)

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 )

mais vous devrez toujours arrondir et comparer les nombres dans votre application au point le plus proche pour certaines opérations. (Principalement les fonctions OrderXXX)


Cela dépend simplement de la façon dont vous écrivez votre code, et si vous voulez faire la différence entre l'arrondi de l'application (où deux nombres différents sont conceptuellement/logiquement traités comme identiques pour des raisons de simplicité/convenance),

et les erreurs de virgule flottante. Il n'y a pas de bon et de mauvais, mais je pense qu'une approche est plus déroutante que l'autre.....


En outre, je suis personnellement un peu sceptique quant aux prémisses non citées (mais je suis ouvert à toute correction !), encore une fois mentionnées dans le message précédent.

Raison: