Può il prezzo != prezzo ? - pagina 5

 

Ho iniziato con questa premessa di base sull'uguaglianza dei prezzi (piuttosto solo l'uguaglianza dei doppi) -

(P1) Supponendo y = 1,50000: x == y, purché x sia un qualsiasi numero reale che sia (i) maggiore o uguale a 1,499995 e (ii) minore di 1,500005.

Partendo da P1, ho concluso che -

(P2). Supponendo y = 1,50000: a == y, b == y, e a == b, purché a e b siano numeri reali che siano (i) maggiori o uguali a 1,499995 e (ii) minori di 1,500005.

Gli esempi includono: 1,500055 == 1,50006, 1,500055 == 1,500064, 1,500051 != 1,500059, e 1,500054 != 1,500056.

Usando quanto sopra, ho creato una funzione (sotto) che (1) prende due prezzi come argomenti, (2) arrotonda quei prezzi al punto equivalente più vicino, e (3) determina se quei due prezzi sono uguali.

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

Questa funzione è semplice e diretta, ma dovrei fare qualche commento sulla parte "Condizionamento del prezzo". Come molti di noi sanno, i doppi (cioè, Ho scoperto che quando ho arrotondato 1.5000551 e 1.5000550 al punto più vicino e ho confrontato il risultato (1.50006 e 1.50005, rispettivamente), sembravano non essere uguali anche se, sotto P1 e P2 sopra, dovrebbero essere uguali. Ho concluso (dopo aver condotto un paio di prove) che il letterale 1.5000550 era memorizzato nella variabile come ~1.5000549999. Per rimediare, ho deciso che se il prezzo è entro 15 decimillesimi di punto dal punto intermedio (x.xxxxx5), presumo che il prezzo abbia raggiunto la soglia minima per l'arrotondamento al punto più vicino. Di conseguenza, aggiungo 15 decimillesimi di punto ad ogni prezzo prima di arrotondare al punto più vicino. In questo momento, non credo che questa aggiunta abbia conseguenze indesiderate. Inoltre, questi valori possono essere regolati per aumentare o diminuire l'ipotesi di arrotondamento al punto più vicino.

RaptorUK e WHRoeder(e altri):usando quanto sopra come modello, ho costruito la seguente funzione chiamata ComparePrices() che si basa sul precedente post di 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);
   }    
}

Come sempre, commenti istruttivi/costruttivi sono benvenuti. :)

 

Ho giocato un po' con questo, cercando di raggiungere un compromesso accettabile tra leggibilità e prestazioni.


Ho optato per le funzioni individuali eq(a,b), ne(a,b), lt(a,b) ecc.


Per esempio

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


Per quanto riguarda le prestazioni sulla mia lenta VM per 4999999 iterazioni ottengo le seguenti misurazioni di base:

Ciclo vuoto: 370ms

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

Funzione bool vuota: 4266ms <-- Sto puntando ad avvicinarmi il più possibile a questa cifra.

Le implementazioni di eq() più veloci che ho gestito sono qui sotto.

Sono circa 2,3 volte più lente della chiamata inline MathsAbs() e 1,3 volte più lente di una chiamata di funzione booleana vuota.

Inoltre, per inciso, ho scoperto che MQL non cortocircuita le espressioni booleane.

bool eq(double a,double b) {

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

}

in 5558ms

O se preferite le statiche alle globali (per mantenere tutto il codice in un unico posto):

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() ecc. dovrebbero essere più veloci perché eq() e ne() sono più complicate.

 
RaptorUK: Quindi come faccio a far sì che TestValue sia uguale a 1.57373 non > o < ?

Non lo fai. La virgola mobile non è MAI esatta per alcuni numeri.

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

I numeri in virgola mobile sono numeri razionali perché possono essere rappresentati come un intero diviso per un altro. Per esempio 1,45×103 è (145/100)*1000 o 145000/100. La base però determina le frazioni che possono essere rappresentate. Per esempio, 1/5 non può essere rappresentato esattamente come un numero in virgola mobile usando una base binaria ma può essere rappresentato esattamente usando una base decimale (0.2, o 2×10-1). Tuttavia 1/3 non può essere rappresentato esattamente né in binario (0,010101...) né in decimale (0,333....), ma in base 3 è banale (0,1 o 1×3-1) .
Ecco perché dico di non usare MAI e poi mai NormalizeDouble. È un Kludge. Il suo uso è SEMPRE sbagliato.
 
Thirteen:

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

Il valore doppio dal broker potrebbe essere ovunque da 1,23457500000000 a 1,23458499999999999 ed essere ancora considerato lo stesso prezzo di 1,23458.

Eppure la vostra funzione dice che 1.234575000000000000 NON è GToE di 1.2345849999999999999

Eppure la tua funzione dice che 1,23458499999999999999 è NON LToE di 1,23457500000000

Devi usare un punto/2 nei confronti https://www.mql5.com/en/forum/136997/page3#780837

 
ydrol:

Ho giocato un po' con questo, cercando di raggiungere un compromesso accettabile tra leggibilità e prestazioni.

Credo che 0.0 sia un caso speciale, quindi si può testare direttamente con 0.0
 
WHRoeder:

Il valore doppio dal broker potrebbe essere ovunque da 1.23457500000000 a 1.23458499999999999 ed essere ancora considerato lo stesso prezzo di 1.23458.

Sono generalmente d'accordo. Vedi i miei P1 e P2 nel mio post precedente.

WHRoeder:

Eppure la tua funzione dice che 1,234575000000000000 NON è GToE di 1,2345849999999999999

Eppure la tua funzione dice che 1.23458499999999999999 è NON LToE di 1.234575000000000000

Il problema nasce da come MT4/MQL memorizza i valori in virgola mobile nelle variabili. Per esempio:

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

stampa le due variabili nel log/journal:

Test ComparePrices #1

Come potete vedere, p2 non è più 1.23458499999999999, ma diventa invece 1.23458500 - a causa, credo, degli arrotondamenti. Questa è la ragione per cui la mia funzione dice che p1 non è GToE a p2; e come potete vedere nel codice qui sotto, anche il vostro codice suggerisce lo stesso - cioè, che p1 non è GToE a p2 e che p1 non è uguale 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.);

Test ComparePrices #2

Dovete usare un punto/2 nei confronti

C'è la possibilità che il punto/2 sia troppo piccolo come deviazione massima. Per esempio:

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 ComparePrices #3

Se l'ipotesi è che 1.234575 sia uguale a 1.234580, allora perché il #2 mostra NEQ? Inoltre, se assumiamo che 1.23458 è un prezzo che potrebbe significare un prezzo dal broker che è ovunque da 1.23457500000000 a 1.23458499999999999, perché #1 dovrebbe mostrare NEQ? Non dovrebbero essere uguali se condividono lo stesso punto di prezzo (da qui la mia Premessa #2 nel mio post precedente)?

 

@Thirteen,


Nel tuo codice stai guardando le differenze di arrotondamento intenzionali dovute alla logica dell'applicazione, non gli errori di arrotondamento involontari dovuti a errori in virgola mobile, da cui la differenza:

I due tipi di "arrotondamento" sono:

a) Errori intrinseci di arrotondamento dovuti alle frazioni binarie nel formato IEEE. - Questi numeri dovrebbero essere esattamente gli stessi, ma non lo sono a causa della rappresentazione binaria delle frazioni decimali. Sono arrotondati dalla rappresentazione MQ4 dei decimali.

b) Arrotondamento esplicito a qualche numero o decimale. (per esempio quando si stampa, o si inviano i prezzi a un Broker). - Questi non sono realmente destinati ad essere gli stessi valori, invece vengono arrotondati dalla logica dell'applicazione per la convenienza di qualcuno.

Questo non è realmente un errore. È improbabile che gli errori dovuti esclusivamente alla rappresentazione in virgola mobile diventino così grandi (a meno che non si calcoli male una serie). Ma potreste voler fare questo tipo di confronto nella vostra applicazione secondo la vostra logica.


Gli errori di arrotondamento intrinseci[a] sono di solito molto piccoli (ordini di grandezza inferiori a Punto) e non intenzionali. L'applicazione non è in grado di arrotondare questi numeri per essere esattamente il valore desiderato, utilizzando il tipo di dati doppio .

Le differenze di arrotondamento esplicito[b] sono intenzionali e molto più grandi (+/- 0,5 punti). (in questo caso). quindi due numeri arrotondati dalla vostra logica applicativa allo stesso valore di punto possono essere quasi un punto intero di distanza in origine.


Idealmente arrotonderei prima i numeri [b] (solo se l'arrotondamento è richiesto) e poi li confronterei [a] a quel punto l'errore è molto piccolo a causa delle limitazioni del doppio. (es. < 0.0000001)

Ma il tuo codice è per confrontare prima di arrotondare, nel qual caso devi dettagliare con le possibili differenze molto più grandi. Comunque l'arrotondamento non è sempre richiesto. Lo userei solo quando si inviano i prezzi al broker.


Pensatela in un altro modo (se MQ4 avesse usato Binary Coded Decimal - che permette l'esatta rappresentazione delle frazioni decimali - allora tutti i problemi riguardanti Price != Price andrebbero via,

ma dovresti comunque arrotondare e confrontare i numeri nella tua applicazione al punto più vicino per certe operazioni. (Principalmente funzioni OrderXXX)


>> "se assumiamo che 1.23458 è un prezzo che potrebbe significare un prezzo dal broker che è ovunque da 1.23457500000000 a 1.23458499999999999"

Potrei sbagliarmi (non sono sicuro di come funzionano i broker) ma penso che un prezzo dal broker di 1,23458 sia esattamente quello. specialmente con lotti da 100.000 dollari e più grandi da considerare. Altrimenti ci sono un sacco di soldi da fare (per il broker) sfruttando la differenza dei prezzi pubblicati.

La mia comprensione è che è davvero solo quando si invia al broker che si deve arrotondare, non in tutta la vostra applicazione. Nel qual caso i confronti per piccoli errori dovrebbero essere sufficienti.

L'imprecisione in virgola mobile è separata dall'arrotondamento per i prezzi del broker. Ma se volete trattarli entrambi allo stesso tempo, immagino che sia una preferenza personale (potrebbe confondersi però?)

 

Ecco la mia versione completa, (si spera senza bug).

Questo fornisce 6 funzioni:

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


Il razionale è quello di mantenere il codice leggibile (IMO), e ridurre la probabilità di errori di battitura, senza un eccessivo impatto sulle prestazioni.

A tutti gli effetti queste funzioni dovrebbero essere veloci come può essere fatto usando le funzioni utente di MQ4,

(per le prestazioni rispetto a MathAbs(a-b) < HalfPoint vedere https://www.mql5.com/en/forum/136997/page5#822505 anche se in un EA reale (al contrario di un benchmark) sospetto che la differenza sia 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:

Ecco la mia versione completa (spero non ci siano bug).

...

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 premessa spesso citata è:

  • Il valore doppio dal broker potrebbe essere ovunque da 1.23457500000000 a 1.23458499999999999 ed essere ancora considerato lo stesso prezzo 1.23458.

Considerando questa premessa e usando il tuo codice come sfondo, potresti spiegarmi perché stai dicendo che (a) 1.234576 e 1.234584 sono considerati non uguali, (b) 1.234577 e 1.234583 sono considerati non uguali, ma (c) 1.234578 e 1.234582 sono considerati uguali? Perché (e come) l'esempio (b) è meno uguale dell'esempio (c)?

Come ho detto sopra, considero tutti questi prezzi uguali perché condividono lo stesso punto di prezzo, cioè 1,23458. Questo esempio illustra il motivo per cui credo (e ho detto sopra) che Point/2 potrebbe essere una deviazione massima troppo piccola.

 

@Thirteen, la mia risposta alle tue osservazioni rimane la stessa 3 post sopra https://www.mql5.com/en/forum/136997/page5#822672 link. Ripeto la parte che potrebbe portare al momento luce-bulbo nella comprensione del mio punto di vista: (con un po' di revisione ed enfasi aggiunta)

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 )

ma dovreste ancora arrotondare e confrontare i numeri nella vostra applicazione al punto più vicino per certe operazioni. (Principalmente le funzioni OrderXXX)


Dipende solo da come scrivete il vostro codice, e se volete differenziare tra l'arrotondamento dell'applicazione (dove due numeri diversi sono concettualmente/logicamente trattati come lo stesso per semplicità/convenienza),

e gli errori in virgola mobile. Non c'è un giusto e uno sbagliato, ma penso che un approccio sia più confuso dell'altro....


Inoltre, sono personalmente un po' scettico sulla premessa non citata (ma aperta alla correzione!), di nuovo menzionata nel post precedente.

Motivazione: