Kann Preis != Preis ? - Seite 5

 

Ich bin von der folgenden Grundannahme über die Gleichheit der Preise ausgegangen (oder vielmehr von der Gleichheit des Doppelten)

(P1): Angenommen y = 1,50000: x == y, solange x eine beliebige reelle Zahl ist, die (i) größer oder gleich 1,499995 und (ii) kleiner als 1,500005 ist.

Aufbauend auf P1 habe ich gefolgert, dass -

(P2). y = 1,50000 vorausgesetzt: a == y, b == y und a == b, sofern a und b reelle Zahlen sind, die (i) größer oder gleich 1,499995 und (ii) kleiner als 1,500005 sind.

Beispiele sind: 1.500055 == 1.50006, 1.500055 == 1.500064, 1.500051 != 1.500059 und 1.500054 != 1.500056.

Auf dieser Grundlage habe ich eine Funktion erstellt (siehe unten), die (1) zwei Preise als Argumente annimmt, (2) diese Preise auf den nächstgelegenen Punkt rundet und (3) feststellt, ob diese beiden Preise gleich sind.

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

Diese Funktion ist einfach und geradlinig, aber ich sollte etwas über den Teil "Preiskonditionierung" sagen. Wie viele von uns wissen, sind Doubles (d.h., Wie viele von uns wissen, sind Double-Precision-Variablen (d. h. Variablen im Fließkommaformat ) manchmal etwas ungenau und haben gelegentlich Rundungsprobleme. Als ich 1,5000551 und 1,5000550 auf den nächsten Punkt rundete und das Ergebnis (1,50006 bzw. 1,50005) verglich, stellte ich fest, dass sie nicht gleich waren, obwohl sie gemäß P1 und P2 oben gleich sein sollten. Ich kam zu dem Schluss (nach einigen Tests), dass das Literal 1,5000550 in der Variablen als ~1,5000549999 gespeichert war. Um dieses Problem zu beheben, beschloss ich, dass ich davon ausgehe, dass der Preis die Mindestschwelle für das Aufrunden auf den nächsten Punkt erreicht hat, wenn er innerhalb von 15 Zehntausendsteln eines Punktes vom Mittelpunkt (x.xxxxx5) liegt. Dementsprechend addiere ich zu jedem Preis 15 Zehntausendstel eines Punktes, bevor ich auf den nächsten Punkt aufrunde. Derzeit glaube ich nicht, dass dieser Zusatz unbeabsichtigte Folgen hat. Außerdem können diese Werte angepasst werden, um die Annahme für das Aufrunden auf den nächsten Punkt zu erhöhen/aufzulösen.

RaptorUK und WHRoeder(und andere):Auf der Grundlage der obigen Ausführungen habe ich die folgende Funktion namens ComparePrices() entwickelt, die auf RaptorUKs vorherigem Beitrag basiert:

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

Wie immer sind lehrreiche/konstruktive Kommentare willkommen. :)

 

Ich habe selbst ein wenig damit herumgespielt und versucht, einen akzeptablen Kompromiss aus Lesbarkeit und Leistung zu finden.


Ich habe mich für die einzelnen Funktionen eq(a,b), ne(a,b), lt(a,b) usw. entschieden.


z.B.

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


Bezüglich der Leistung auf meiner langsamen VM erhalte ich für 4999999 Iterationen folgende Basismessungen:

Leere Schleife: 370ms

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

Leere boolsche Funktion: 4266ms <-- Ich versuche, so nah wie möglich an diese Zahl heranzukommen.

Die schnellsten eq()-Implementierungen, die ich gefunden habe, sind unten aufgeführt.

Sie sind etwa 2,3 mal langsamer als der Inline-Aufruf von MathsAbs() und 1,3 mal langsamer als ein leerer boolescher Funktionsaufruf, der einfach nur true zurückgibt.

Ganz nebenbei habe ich entdeckt, dass MQL boolsche Ausdrücke nicht kurzschließt.

bool eq(double a,double b) {

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

}

in 5558ms

Oder wenn Sie Statik den Globals vorziehen (um den gesamten Code an einem Ort zu halten):

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

in 5718ms


lt(), gt() usw. sollten schneller sein, da eq() und ne() komplizierter sind.

 
RaptorUK: Wie erreiche ich also, dass TestValue gleich 1,57373 und nicht > oder < ist?

Gar nicht. Gleitkommazahlen sind bei einigen Zahlen NIE exakt.

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

Fließkommazahlen sind rationale Zahlen, weil sie als eine ganze Zahl geteilt durch eine andere dargestellt werden können. Zum Beispiel 1,45×103 ist (145/100)*1000 oder 145000/100. Die Basis bestimmt jedoch, welche Brüche dargestellt werden können. Beispielsweise kann 1/5 nicht exakt als Fließkommazahl mit einer binären Basis dargestellt werden, wohl aber mit einer dezimalen Basis (0,2 oder 2×10-1). 1/3 kann jedoch weder binär (0,010101...) noch dezimal (0,333....) exakt dargestellt werden, aber zur Basis 3 ist es trivial (0,1 oder 1×3-1).
Deshalb sage ich: NIE, NIEMALS NormalizeDouble verwenden. Es ist ein Klotz am Bein. Seine Verwendung ist IMMER falsch.
 
Thirteen:

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

Der doppelte Wert des Brokers könnte irgendwo zwischen 1,234575000000000000 und 1,23458499999999999 liegen und immer noch als derselbe Preis von 1,23458 angesehen werden.

Dennoch sagt Ihre Funktion, dass 1,234575000000000000 NICHT GToE von 1,23458499999999999 ist.

Dennoch sagt Ihre Funktion, dass 1,2345849999999999 NICHT LToE von 1,234575000000000000 ist.

Sie müssen einen Punkt/2 in den Vergleichen verwenden https://www.mql5.com/en/forum/136997/page3#780837

 
ydrol:

Ich habe selbst ein wenig damit herumgespielt und versucht, einen akzeptablen Kompromiss aus Lesbarkeit und Leistung zu finden.

Ich glaube, dass 0.0 ein Sonderfall ist, so dass Sie direkt mit 0.0 testen können
 
WHRoeder:

Der doppelte Wert des Brokers könnte irgendwo zwischen 1,234575000000000000 und 1,23458499999999999 liegen und immer noch als derselbe Preis von 1,23458 angesehen werden.

Ich stimme im Allgemeinen zu. Siehe meine P1 und P2 in meinem obigen Beitrag.

WHRoeder:

Doch Ihre Funktion sagt, dass 1,234575000000000000 NICHT GToE von 1,23458499999999999 ist

Dennoch sagt Ihre Funktion, dass 1.2345849999999999 NICHT LToE von 1.234575000000000000 ist

Das Problem ergibt sich aus der Art und Weise, wie MT4/MQL Fließkommawerte in Variablen speichert. Zum Beispiel:

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

Druckt die beiden Variablen im Log/Journal aus:

ComparePrices Test #1

Wie Sie sehen können, ist p2 nicht mehr 1,23458499999999999, sondern wird zu 1,23458500 - ich glaube, das liegt am Abrunden. Das ist der Grund, warum meine Funktion sagt, dass p1 nicht GToE zu p2 ist; und wie Sie im Code unten sehen können, suggeriert auch Ihr Code dasselbe - d.h., dass p1 nicht GToE zu p2 ist und dass p1 nicht gleich p2 ist.

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

ComparePrices Test #2

Sie müssen einen Punkt/2 in den Vergleichen verwenden

Es besteht die Möglichkeit, dass Punkt/2 eine zu kleine maximale Abweichung ist. Zum Beispiel:

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

ComparePrices Test #3

Wenn die Annahme ist, dass 1,234575 gleich 1,234580 ist, warum zeigt #2 dann NEQ? Wenn wir außerdem davon ausgehen, dass 1,23458 ein Preis ist, der einen Preis des Brokers bedeuten könnte, der irgendwo zwischen 1,234575000000000000 und 1,23458499999999999 liegt, warum sollte #1 dann NEQ anzeigen? Sollten sie nicht gleich sein, wenn sie denselben Preispunkt haben (daher meine Prämisse #2 in meinem obigen Beitrag)?

 

@Dreizehn,


Bei Ihrem Code handelt es sich um gewollte Rundungsdifferenzen aufgrund der Anwendungslogik und nicht um ungewollte Rundungsfehler aufgrund von Fließkommafehlern, daher der Unterschied:

Es gibt zwei Arten von "Rundungen":

a) Intrinsische Rundungsfehler aufgrund von Binärbrüchen im IEEE-Format. - Diese Zahlen sollten eigentlich genau gleich sein, sind es aber aufgrund der binären Darstellung von Dezimalbrüchen nicht. Sie werden durch die MQ4-Darstellung von Dezimalzahlen gerundet.

b) Explizite Rundung auf eine bestimmte Anzahl von Dezimalstellen. (z.B. beim Drucken oder beim Senden von Preisen an einen Broker). - Es handelt sich nicht wirklich um dieselben Werte, sondern sie werden von der Anwendungslogik aus Bequemlichkeit gerundet.

Dies ist nicht wirklich ein Fehler. Fehler, die ausschließlich auf die Fließkommadarstellung zurückzuführen sind, werden wahrscheinlich nicht so groß werden (es sei denn, die Berechnung einer Reihe ist schlecht). Aber vielleicht möchten Sie diese Art von Vergleich in Ihrer Anwendung nach Ihrer eigenen Logik durchführen.


Die intrinsischen Rundungsfehler[a] sind in der Regel sehr klein (Größenordnungen kleiner als Punkt) und unbeabsichtigt. Die Anwendung ist nicht in der Lage, diese Zahlen so zu runden, dass sie genau dem beabsichtigten Wert entsprechen, da der Datentyp double verwendet wird.

Die expliziten Rundungsdifferenzen[b] sind gewollt und viel größer (+/- 0,5 Punkt). (in diesem Fall). Zwei Zahlen, die von Ihrer Anwendungslogik auf denselben Punktwert gerundet werden, können also ursprünglich fast einen ganzen Punkt auseinander liegen.


Idealerweise würde ich die Zahlen zuerst runden [b] (nur wenn eine Rundung erforderlich ist) und sie dann vergleichen [a], wobei der Fehler aufgrund der Beschränkungen von Double sehr gering ist. (z. B. < 0,0000001)

Aber Ihr Code ist für den Vergleich vor dem Runden, in diesem Fall müssen Sie sich mit den viel größeren möglichen Differenzen befassen. Die Rundung ist jedoch nicht immer erforderlich. Ich würde sie nur verwenden, wenn ich Preise an den Broker sende.


Stellen Sie es sich anders vor (Wenn MQ4 binär kodierte Dezimalzahlen verwendet hätte - was eine exakte Darstellung von Dezimalbrüchen erlaubt - dann wären alle Probleme bezüglich Preis != Preis verschwunden,

aber Sie müssten in Ihrer Anwendung bei bestimmten Operationen trotzdem runden und Zahlen auf die nächste Stelle vergleichen. (Hauptsächlich OrderXXX-Funktionen)


>> "wenn wir annehmen, dass 1.23458 ein Preis ist, könnte das einen Preis vom Broker bedeuten, der irgendwo zwischen 1.234575000000000000 und 1.23458499999999999 liegt"

Ich könnte mich hier irren (ich weiß nicht, wie Broker arbeiten), aber ich denke, ein Preis von 1,23458 vom Broker ist genau das. vor allem mit $100.000 Lotgrößen und größer zu berücksichtigen. Andernfalls kann der Broker eine Menge Geld verdienen, indem er die Differenz zwischen den veröffentlichten Preisen ausnutzt.

Meines Erachtens muss nur bei der Übermittlung an den Broker gerundet werden, nicht in der gesamten Anwendung. In diesem Fall sollten Vergleiche für kleine Fehler ausreichen.

Die Fließkomma-Ungenauigkeit ist von der Rundung der Broker-Preise getrennt. Aber wenn Sie mit beiden gleichzeitig umgehen wollen, ist das wohl Ihre persönliche Vorliebe (könnte aber verwirrend werden?)

 

Hier ist meine vollständige Version (hoffentlich ohne Bugs).

Dies bietet 6 Funktionen:

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


Der Grund dafür ist, den Code lesbar zu halten (IMO) und die Wahrscheinlichkeit von Tippfehlern zu verringern, ohne die Leistung zu sehr zu beeinträchtigen.

In jeder Hinsicht sollten diese Funktionen so schnell sein, wie es mit MQ4-Benutzerfunktionen möglich ist,

(zur Leistung im Vergleich zu MathAbs(a-b) < HalfPoint siehe https://www.mql5.com/en/forum/136997/page5#822505, obwohl ich vermute, dass der Unterschied in einem echten EA (im Gegensatz zu einem Benchmark) unbedeutend ist.


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:

Hier ist meine vollständige Version (hoffentlich ohne 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);
   }
}

Die oft zitierte Prämisse lautet:

  • Der doppelte Wert des Brokers könnte irgendwo zwischen 1,234575000000000000 und 1,23458499999999999 liegen und immer noch als derselbe Preis von 1,23458 angesehen werden.

Könnten Sie mir in Anbetracht dieser Prämisse und unter Verwendung Ihres Codes erklären, warum Sie sagen, dass (a) 1,234576 und 1,234584 als nicht gleich angesehen werden, (b) 1,234577 und 1,234583 als nicht gleich angesehen werden, aber (c) 1,234578 und 1,234582 als gleich angesehen werden? Warum (und wie) ist Beispiel (b) weniger gleich als Beispiel (c)?

Wie ich oben dargelegt habe, betrachte ich alle diese Kurse als gleichwertig, weil sie denselben Preispunkt haben, nämlich 1,23458. Dieses Beispiel veranschaulicht, warum ich glaube (und oben gesagt habe), dass Punkt/2 eine zu kleine maximale Abweichung sein könnte.

 

@Thirteen, meine Antwort auf Ihre Beobachtungen bleibt die gleiche wie 3 Beiträge weiter oben https://www.mql5.com/en/forum/136997/page5#822672 link. Ich werde den Teil wiederholen, der zum Verständnis meines Standpunkts beitragen könnte: (mit ein wenig Überarbeitung und Betonung)

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 )

aber Sie müssten in Ihrer Anwendung bei bestimmten Operationen immer noch Zahlen runden und auf den nächsten Punkt vergleichen. (Hauptsächlich OrderXXX-Funktionen)


Es hängt einfach davon ab, wie Sie Ihren Code schreiben und ob Sie zwischen der Anwendungsrundung (bei der zwei unterschiedliche Zahlen der Einfachheit halber konzeptionell/logisch als gleich behandelt werden) und Fließkommafehlern unterscheiden wollen,

und Fließkommafehlern. Es gibt kein richtig oder falsch, aber ich denke, dass der eine Ansatz verwirrender ist als der andere....


Außerdem bin ich persönlich ein wenig skeptisch gegenüber der nicht zitierten Prämisse (aber offen für Korrekturen!), die ebenfalls in einem früheren Beitrag erwähnt wurde.