Can price != price ? - page 5

 

I started with this basic premise about the equality of prices (rather just the equality of doubles) -

(P1).  Assuming y = 1.50000: x == y, so long as x is any real number that is (i) greater than or equal to 1.499995 and (ii) less than 1.500005. 

Building on P1, I concluded that -

(P2).  Assuming y = 1.50000: a == y, b == y, and a == b, so long as a and b are real numbers that are (i) greater than or equal to 1.499995 and (ii) less than 1.500005. 

Examples include:  1.500055 == 1.50006,  1.500055 == 1.500064,  1.500051 != 1.500059,  and 1.500054 != 1.500056.

Using the above, I created a function (below) that (1) takes two prices as arguments, (2) rounds those prices to the nearest point equivalent, and (3) determines whether those two prices are equal. 

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

This function is simple and straight forward, but I should comment some about the "Price Conditioning" portion.  As many of us know, doubles (i.e., double precision floating-point format variables) are, at times, slightly imprecise and have occasional rounding issues.  I found that when I rounded 1.5000551 and 1.5000550 to the nearest point and compared the result (1.50006 and 1.50005, respectively), they appeared to be not equal even though, under P1 and P2 above, they should be equal.  I concluded (after conducting a couple of tests) that the literal 1.5000550 was stored in the variable as ~1.5000549999.  To remedy this, I decided that if price was within 15 ten-thousandths of a point from the halfway point (x.xxxxx5), I would assume that price has met the minimum threshold for rounding up to nearest point.  Accordingly, I add 15 ten-thousandths of a point to each price before I round to the nearest point.  At this time, I don't believe this addition as any unintended consequences.  Moreover, those values can be adjusted to increase/decease the assumption for rounding up to the nearest point. 

RaptorUK and WHRoeder (and others):  Using the above as a blueprint, I constructed the following function called ComparePrices() which is based on RaptorUK's previous post:

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

As always, instructive/constructive comments are welcome. :) 

 

I've had a little play with this myself - trying to reach an acceptable compromise of readability and performance.


I have settled for individual functions eq(a,b), ne(a,b), lt(a,b) etc...


Eg

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


Regarding performance on my slow VM for 4999999 iterations I get following baseline measurements:

Empty loop : 370ms

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

Empty bool Function: 4266ms <-- I'm aiming to get as close to this figure as possible.

The fastest eq() implementation I managed are below.

They are about 2.3 times slower than inline MathsAbs() call and 1.3 times slower than an empty boolean function call .that just returns true.

Also as an aside I discovered MQL does not short circuit boolean expressions.

bool eq(double a,double b) {

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

}

in 5558ms

Or if you prefer statics to globals (to keep all code in one place):

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() etc should be faster as eq() and ne() are more complicated.

 
RaptorUK: So how do I get TestValue to be equal to 1.57373 not > or < ?

You don't. floating point is NEVER exact for some numbers.

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

Floating-point numbers are rational numbers because they can be represented as one integer divided by another. For example 1.45×103 is (145/100)*1000 or 145000/100. The base however determines the fractions that can be represented. For instance, 1/5 cannot be represented exactly as a floating-point number using a binary base but can be represented exactly using a decimal base (0.2, or 2×10−1). However 1/3 cannot be represented exactly by either binary (0.010101...) nor decimal (0.333....), but in base 3 it is trivial (0.1 or 1×3−1) .
That is why I say NEVER, EVER use NormalizeDouble. It is a Kludge. Its use is ALWAYS wrong.
 
Thirteen:

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

The double value from the broker could be anywhere from 1.234575000000000000 through 1.23458499999999999 and still be considered the same 1.23458 price.

Yet your function says that 1.234575000000000000 is NOT GToE of 1.23458499999999999

Yet your function says that 1.234584999999999999 is NOT LToE of 1.234575000000000000

You must use a point/2 in the comparisons https://www.mql5.com/en/forum/136997/page3#780837

 
ydrol:

I've had a little play with this myself - trying to reach an acceptable compromise of readability and performance.

I believe that 0.0 is a special case so you can test directly with 0.0
 
WHRoeder:

The double value from the broker could be anywhere from 1.234575000000000000 through 1.23458499999999999 and still be considered the same 1.23458 price.

I generally agree. See my P1 and P2 in my above post.

WHRoeder:

Yet your function says that 1.234575000000000000 is NOT GToE of 1.23458499999999999

Yet your function says that 1.234584999999999999 is NOT LToE of 1.234575000000000000

The problem arises from how MT4/MQL stores floating-point values in variables. For example:

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

prints the two variables in the log/journal:

ComparePrices test #1

As you can see, p2 is no longer 1.23458499999999999, but instead becomes 1.23458500--due, I believe, to round off. That is the reason why my function says that p1 is not GToE to p2; and as you can see in the code below, your code also suggests the same--i.e., that p1 is not GToE to p2 and that p1 is Not Equal to 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.);

ComparePrices test #2

You must use a point/2 in the comparisons

There is a possibility that Point/2 is too small of a max deviation. For example:

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

If the assumption is that 1.234575 is equal to 1.234580, then why does #2 show NEQ? Moreover, if we assume that 1.23458 is a price that could mean a price from the broker that is anywhere from 1.234575000000000000 through 1.23458499999999999, why should #1 show NEQ? Shouldn't they be equal if they share the same price point (hence my Premise #2 in my above post)?

 

@Thirteen,


In your code you are looking at intentional rounding differences due to application logic, not unintentional rounding errors due to floating point errors, hence the difference:

The two types of 'rounding' are:

a) Intrinsic rounding errors due to binary fractions in the IEEE format. - these numbers are meant to be exactly the same but are not due to the binary representation of decimal fractions. They are rounded by the MQ4 representation of decimals.

b) Explicit rounding to some number or decimal places. (eg when Printing, or sending prices to a Broker). - these are not really meant to be the same values, instead they are being rounded by the application logic for someone's convenience.

This is not really an error. Errors due to solely to floating point representation will be unlikely to get this large (unless calculating a series badly). But you may want to do this type of comparison in your application according to your own logic.


The intrinsic rounding errors[a] are usually very small ( orders of magnitude less than Point ) and unintentional. The application is not able to round these numbers to be exactly the intended value, using the double datatype.

The explicit rounding differences[b] are intentional and much bigger (+/- 0.5 point). (in this case). so two numbers rounded by your application logic to the same point value can be almost a full point apart originally.


Ideally I would first round the numbers [b] (only if rounding is required) and then compare them [a] at which point the error is very small due to limitations of double. (eg. < 0.0000001)

But your code is for comparing before rounding them, in which case you have to detail with the much larger possible differences. However the rounding is not always required. I would only use it when sending prices to the broker.


Think of it another way (If MQ4 had used Binary Coded Decimal - which allows exact representation of Decimal fractions - then all the issues regarding Price != Price would go away,

but you would still have to Round and compare numbers in your application to the nearest point for certain operations. (Mainly OrderXXX funxtions)


>> "if we assume that 1.23458 is a price that could mean a price from the broker that is anywhere from 1.234575000000000000 through 1.23458499999999999"

I could be wrong here (not sure how brokers work) but I think a price from the broker of 1.23458 is exactly that. especially with $100,000 Lot sizes and bigger to consider. Otherwise a lot of money to be made (by the broker) by exploiting the difference in published prices.

My understanding is that it is really only when sending to the Broker you have to round, not throughout your application. In which case comparisons for small error should suffice.

The floating point inaccuracy is seperate from the rounding for broker prices. But if you want to deal with them both at the same time I guess that is personal preference (might get confusing though?)

 

Here is my complete version, (hopefully no bugs)..

This provides 6 functions:

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


The rational is to keep code readable (IMO), and reduce likelyhood of typing errors, without too much of a performance hit.

For all intents and purposes these functions should be as fast as it can be done using MQ4 user functions,

(for performance vs MathAbs(a-b) < HalfPoint see https://www.mql5.com/en/forum/136997/page5#822505 though in a real EA (as opposed to a benchmark) I suspect the difference is insignificant.


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:

Here is my complete version, (hopefully no 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);
   }
}

The oft-quoted premise is:

  • The double value from the broker could be anywhere from 1.234575000000000000 through 1.23458499999999999 and still be considered the same 1.23458 price.

Considering that premise and using your code as a backdrop, could you explain to me why you are saying (a) 1.234576 and 1.234584 are considered to be not equal, (b) 1.234577 and 1.234583 are considered to be not equal, but (c) 1.234578 and 1.234582 are considered to be equal? Why (and how) is example (b) any less equal than example (c)?

As I have stated above, I consider all of those prices to be equal because each share the same price point--namely, 1.23458. This example illustrates why I believe (and have said above) that Point/2 may be too small of a max deviation.

 

@Thirteen, my answer to your observations remains the same 3 posts above https://www.mql5.com/en/forum/136997/page5#822672 link. I'll repeat the part that might lead to the light-bulb moment in understanding my point of view: (with a bit of revision and emphasis added)

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 )

but you would still have to Round and compare numbers in your application to the nearest point for certain operations. (Mainly OrderXXX funxtions)


It just depends on how you write your code, and if you want to differentiate between application rounding (where two different numbers are conceptually /logically treated as the same for simplicity/convenience),

and floating point errors. There is no right and wrong but I think one approach more confusing than the other....


Additionally, I'm personally a little skeptical of the off-quoted premise (but open to correction!), again mentioned in the earlier post.

Reason: