¿Puede el precio != el precio ? - página 5

 

Empecé con esta premisa básica sobre la igualdad de los precios (más bien sólo la igualdad de los dobles)

(P1). Suponiendo que y = 1,50000: x == y, siempre que x sea cualquier número real que sea (i) mayor o igual que 1,499995 y (ii) menor que 1,500005.

Partiendo de P1, he llegado a la conclusión de que

(P2). Suponiendo y = 1,50000: a == y, b == y, y a == b, siempre que a y b sean números reales que sean (i) mayores o iguales que 1,499995 y (ii) menores que 1,500005.

Algunos ejemplos son: 1,500055 == 1,50006, 1,500055 == 1,500064, 1,500051 != 1,500059 y 1,500054 != 1,500056.

Utilizando lo anterior, he creado una función (abajo) que (1) toma dos precios como argumentos, (2) redondea esos precios al equivalente en puntos más cercano, y (3) determina si esos dos precios son iguales.

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 función es simple y directa, pero debo comentar algo sobre la parte del "Acondicionamiento del precio". Como muchos de nosotros sabemos, los dobles (es decir, Como muchos sabemos, los dobles (es decir, las variables con formato de punto flotante de doble precisión ) son, a veces, ligeramente imprecisos y tienen problemas ocasionales de redondeo. Descubrí que cuando redondeaba 1,5000551 y 1,5000550 al punto más cercano y comparaba el resultado (1,50006 y 1,50005, respectivamente), parecían no ser iguales aunque, según P1 y P2 anteriores, deberían serlo. Concluí (después de realizar un par de pruebas) que el literal 1,5000550 se almacenaba en la variable como ~1,5000549999. Para remediarlo, decidí que si el precio estaba a menos de 15 diezmilésimas de punto del punto medio (x.xxxxx5), asumiría que el precio ha alcanzado el umbral mínimo para redondear al punto más cercano. En consecuencia, añado 15 diezmilésimas de punto a cada precio antes de redondear al punto más cercano. Por el momento, no creo que esta adición tenga ninguna consecuencia no deseada. Además, esos valores pueden ajustarse para aumentar/disminuir la suposición de redondear al punto más cercano.

RaptorUK y WHRoeder(y otros):Utilizando lo anterior como modelo, he construido la siguiente función llamada ComparePrices() que se basa en el post anterior 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);
   }    
}

Como siempre, los comentarios instructivos/constructivos son bienvenidos. :)

 

He jugado un poco con esto, tratando de alcanzar un compromiso aceptable de legibilidad y rendimiento.


Me he conformado con las funciones individuales eq(a,b), ne(a,b), lt(a,b) etc...


Por ejemplo,

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


En cuanto al rendimiento en mi VM lenta para 4999999 iteraciones obtengo las siguientes mediciones de referencia:

Bucle vacío : 370ms

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

Función bool vacía: 4266ms <-- Pretendo acercarme lo más posible a esta cifra.

Las implementaciones más rápidas de eq() que he conseguido son las siguientes.

Son unas 2,3 veces más lentas que la llamada a MathsAbs() en línea y 1,3 veces más lentas que una llamada a una función booleana vacía... que sólo devuelve true.

También como un aparte he descubierto que MQL no cortocircuita las expresiones booleanas.

bool eq(double a,double b) {

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

}

en 5558ms

O si prefieres estáticas a globales (para mantener todo el código en un solo 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);
   }
}

en 5718ms


lt(), gt() etc deberían ser más rápidos ya que eq() y ne() son más complicados.

 
RaptorUK: Entonces, ¿cómo consigo que TestValue sea igual a 1,57373 no > o < ?

No lo haces. El punto flotante NUNCA es exacto para algunos números.

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

Los números en coma flotante son números racionales porque se pueden representar como un entero dividido por otro. Por ejemplo 1,45×103 es (145/100)*1000 o 145000/100. Sin embargo, la base determina las fracciones que pueden representarse. Por ejemplo, 1/5 no puede representarse exactamente como un número de punto flotante utilizando una base binaria, pero puede representarse exactamente utilizando una base decimal (0,2, o 2×10-1). Sin embargo, 1/3 no puede representarse exactamente ni en binario (0,010101...) ni en decimal (0,333....), pero en base 3 es trivial (0,1 o 1×3-1) .
Por eso digo que NUNCA, NUNCA uses NormalizeDouble. Es un Kludge. Su uso es SIEMPRE incorrecto.
 
Thirteen:

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

El valor doble del corredor podría estar en cualquier lugar desde 1,23457500000000 hasta 1,234584999999999 y seguir siendo considerado el mismo precio de 1,23458.

Sin embargo, su función dice que 1,23457500000000 no es GToE de 1,234584999999999

Sin embargo, su función dice que 1,234584999999999999 NO es LToE de 1,234575000000000000

Debes usar un punto/2 en las comparaciones https://www.mql5.com/en/forum/136997/page3#780837

 
ydrol:

Yo mismo he jugado un poco con esto, intentando alcanzar un compromiso aceptable de legibilidad y rendimiento.

Creo que 0.0 es un caso especial, así que puedes probar directamente con 0.0
 
WHRoeder:

El valor doble del corredor podría estar en cualquier lugar desde 1,23457500000000 hasta 1,234584999999999 y seguir siendo considerado el mismo precio de 1,23458.

En general, estoy de acuerdo. Ver mi P1 y P2 en mi post anterior.

WHRoeder:

Sin embargo, su función dice que 1,23457500000000 es NO GToE de 1,234584999999999

Sin embargo, su función dice que 1,2345849999999999 es NO LToE de 1,234575000000000000

El problema surge de cómo MT4/MQL almacena los valores de punto flotante en las variables. Por ejemplo

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

imprime las dos variables en el registro/diario:

Prueba de comparación de precios nº 1

Como puede ver, p2 ya no es 1,234584999999999, sino que se convierte en 1,23458500--debido, creo, al redondeo. Esa es la razón por la que mi función dice que p1 no es GToE a p2; y como puedes ver en el código de abajo, tu código también sugiere lo mismo--es decir, que p1 no es GToE a p2 y que p1 no es 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.);

Prueba de comparación de precios nº 2

Debes usar un punto/2 en las comparaciones

Existe la posibilidad de que el Punto/2 sea una desviación máxima demasiado pequeña. Por ejemplo:

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

Prueba de comparación de precios nº 3

Si la suposición es que 1,234575 es igual a 1,234580, entonces ¿por qué #2 muestra NEQ? Además, si asumimos que 1,23458 es un precio que podría significar un precio del corredor que está en cualquier lugar desde 1,23457500000000 hasta 1,234584999999999, ¿por qué #1 debería mostrar NEQ? ¿No deberían ser iguales si comparten el mismo punto de precio (de ahí mi Premisa #2 en mi post anterior)?

 

@Thirteen,


En tu código estás viendo diferencias de redondeo intencionadas debidas a la lógica de la aplicación, no errores de redondeo no intencionados debidos a errores de coma flotante, de ahí la diferencia:

Los dos tipos de "redondeo" son:

a) Errores de redondeo intrínsecos debidos a fracciones binarias en el formato IEEE. - Estos números deberían ser exactamente iguales pero no lo son debido a la representación binaria de las fracciones decimales. Se redondean por la representación MQ4 de los decimales.

b) Redondeo explícito a algún número o decimales. (por ejemplo, cuando se imprime, o se envían los precios a un Broker). - Estos no son realmente los mismos valores, sino que están siendo redondeados por la lógica de la aplicación para la conveniencia de alguien.

Esto no es realmente un error. Los errores debidos únicamente a la representación en coma flotante no serán probablemente tan grandes (a menos que se calcule mal una serie). Pero usted puede querer hacer este tipo de comparación en su aplicación de acuerdo a su propia lógica.


Los errores de redondeo intrínsecos[a] suelen ser muy pequeños ( órdenes de magnitud inferiores a Punto) y no intencionados. La aplicación no es capaz de redondear estos números para que sean exactamente el valor previsto, utilizando el tipo de datos double .

Las diferencias de redondeo explícitas[b] son intencionadas y mucho mayores (+/- 0,5 puntos). (en este caso). Así que dos números redondeados por su lógica de aplicación al mismo valor de punto pueden ser casi un punto completo de diferencia originalmente.


Lo ideal sería redondear primero los números [b] (sólo si se requiere el redondeo) y luego compararlos [a] en cuyo momento el error es muy pequeño debido a las limitaciones del doble. (por ejemplo, < 0,0000001)

Pero tu código es para comparar antes de redondearlas, en cuyo caso tienes que detallar con las posibles diferencias mucho mayores. Sin embargo el redondeo no siempre es necesario. Yo sólo lo usaría cuando se envíen los precios al broker.


Piénsalo de otra manera (Si MQ4 hubiera usado Decimal Codificado Binario - que permite la representación exacta de fracciones Decimales - entonces todos los problemas relacionados con Precio != Precio desaparecerían,

pero todavía tendría que redondear y comparar los números en su aplicación al punto más cercano para ciertas operaciones. (Principalmente las funciones de OrderXXX)


>> "si asumimos que 1,23458 es un precio que podría significar un precio del corredor que está en cualquier lugar de 1,234575000000000000 hasta 1,234584999999999"

Podría estar equivocado aquí (no estoy seguro de cómo funcionan los corredores) pero creo que un precio del corredor de 1,23458 es exactamente eso. especialmente con tamaños de lote de 100.000 dólares y más grandes a considerar. De lo contrario, un montón de dinero para ser hecho (por el corredor) mediante la explotación de la diferencia en los precios publicados.

Mi entendimiento es que es realmente sólo al enviar al corredor tiene que redondear, no en toda su aplicación. En cuyo caso las comparaciones por pequeño error deberían ser suficientes.

La inexactitud del punto flotante es independiente del redondeo para los precios del corredor. Pero si quieres tratar con ambos al mismo tiempo, supongo que es una preferencia personal (aunque podría ser confuso).

 

Aquí está mi versión completa, (espero que no haya errores)..

Esto proporciona 6 funciones:

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


La razón es mantener el código legible (OMI), y reducir la probabilidad de errores de escritura, sin demasiado de un golpe de rendimiento.

A todos los efectos, estas funciones deben ser tan rápidas como se puede hacer utilizando las funciones de usuario MQ4,

(para el rendimiento frente a MathAbs(a-b) < HalfPoint ver https://www.mql5.com/en/forum/136997/page5#822505 aunque en un EA real (a diferencia de un benchmark) sospecho que la diferencia es 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:

Aquí está mi versión completa, (espero que no haya errores)..

...

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 premisa tan citada es:

  • El valor doble del broker podría estar en cualquier lugar desde 1,23457500000000 hasta 1,234584999999999 y seguir siendo considerado el mismo precio de 1,23458.

Teniendo en cuenta esta premisa y utilizando su código como telón de fondo, ¿podría explicarme por qué dice que (a) 1,234576 y 1,234584 se consideran no iguales, (b) 1,234577 y 1,234583 se consideran no iguales, pero (c) 1,234578 y 1,234582 se consideran iguales? ¿Por qué (y cómo) el ejemplo (b) es menos igual que el ejemplo (c)?

Como he dicho anteriormente, considero que todos esos precios son iguales porque cada uno comparte el mismo punto de precio, es decir, 1,23458. Este ejemplo ilustra por qué creo (y he dicho más arriba) que Punto/2 puede ser una desviación máxima demasiado pequeña.

 

@Thirteen, mi respuesta a tus observaciones sigue siendo la misma 3 posts más arriba https://www.mql5.com/en/forum/136997/page5#822672 enlace. Voy a repetir la parte que podría conducir a la luz momento en la comprensión de mi punto de vista: (con un poco de revisión y énfasis añadido)

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 )

pero todavía tendría que redondear y comparar números en su aplicación al punto más cercano para ciertas operaciones. (Principalmente las funciones de OrderXXX)


Sólo depende de cómo escribas tu código, y de si quieres diferenciar entre el redondeo en la aplicación (donde dos números diferentes son conceptualmente/lógicamente tratados como el mismo por simplicidad/conveniencia),

y los errores de punto flotante. No hay un bien y un mal, pero creo que un enfoque más confuso que el otro....


Además, personalmente soy un poco escéptico de la premisa fuera de cita (¡pero abierto a la corrección!), de nuevo mencionada en el post anterior.