Может ли цена != цена ? - страница 5

 

Я начал с базовой предпосылки о равенстве цен (скорее, просто равенстве парных цен).

(P1). Предполагая y = 1.50000: x == y, если x - любое действительное число, которое (i) больше или равно 1.499995 и (ii) меньше 1.500005.

Основываясь на P1, я пришел к выводу, что -

(P2). При условии y = 1.50000: a == y, b == y и a == b, если a и b - действительные числа, которые (i) больше или равны 1.499995 и (ii) меньше 1.500005.

Примеры: 1.500055 == 1.50006, 1.500055 == 1.500064, 1.500051 != 1.500059 и 1.500054 != 1.500056.

Используя вышесказанное, я создал функцию (см. ниже), которая (1) принимает две цены в качестве аргументов, (2) округляет эти цены до ближайшего точечного эквивалента и (3) определяет, равны ли эти две цены.

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

Эта функция проста и понятна, но я должен немного прокомментировать часть "Округление цены". Как многие из нас знают, удвоенные (т.е, Я обнаружил, что когда я округлил 1.5000551 и 1.5000550 до ближайшей точки и сравнил результат (1.50006 и 1.50005, соответственно), они оказались не равны, хотя, согласно P1 и P2 выше, они должны быть равны. Я пришел к выводу (после проведения пары тестов), что литерал 1.5000550 хранится в переменной как ~1.5000549999. Чтобы исправить это, я решил, что если цена находится в пределах 15 десятитысячных пункта от точки половины пути (x.xxxxx5), то я буду считать, что цена достигла минимального порога для округления до ближайшего пункта. Соответственно, я добавляю 15 десятитысячных пункта к каждой цене перед округлением до ближайшего пункта. На данный момент я не считаю, что это добавление имеет какие-либо непредвиденные последствия. Более того, эти значения могут быть скорректированы для увеличения или уменьшения предположения для округления до ближайшего пункта.

RaptorUK и WHRoeder(и другие):Используя вышесказанное в качестве образца, я создал следующую функцию ComparePrices(), которая основана на предыдущем сообщении 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);
   }    
}

Как всегда, приветствуются поучительные/конструктивные комментарии. :)

 

Я сам немного поиграл с этим - пытался достичь приемлемого компромисса между читабельностью и производительностью.


Я остановился на отдельных функциях eq(a,b), ne(a,b), lt(a,b) и т.д....


Например, .

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


Что касается производительности на моей медленной виртуальной машине для 4999999 итераций я получаю следующие базовые измерения:

Пустой цикл : 370 мс

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

Пустая bool-функция: 4266 мс <-- Я стремлюсь приблизиться к этой цифре как можно ближе.

Ниже приведены самые быстрые реализации eq(), которые мне удалось найти.

Они примерно в 2.3 раза медленнее, чем вызов inline MathsAbs() и в 1.3 раза медленнее, чем вызов пустой функции boolean, которая просто возвращает true.

Также в качестве дополнения я обнаружил, что MQL не замыкает булевы выражения.

bool eq(double a,double b) {

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

}

5558 мс

Или если вы предпочитаете статику глобалам (чтобы держать весь код в одном месте):

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

за 5718 мс


lt(), gt() и т.д. должны быть быстрее, поскольку eq() и ne() сложнее.

 
RaptorUK: Итак, как мне заставить TestValue быть равным 1.57373 не > или <?

Никак. Плавающая точка НИКОГДА не является точной для некоторых чисел.

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

Числа с плавающей точкой - это рациональные числа, потому что они могут быть представлены как одно целое число, деленное на другое. Например, 1,45×103 - это (145/100)*1000 или 145000/100. Однако основание определяет, какие дроби могут быть представлены. Например, 1/5 не может быть точно представлена как число с плавающей точкой с двоичным основанием, но может быть точно представлена с десятичным основанием (0,2, или 2×10-1). Однако 1/3 не может быть точно представлена ни в двоичной (0.010101...), ни в десятичной (0.333....) системе счисления, но в базе 3 это тривиально (0.1 или 1×3-1).
Вот почему я говорю НИКОГДА, НИКОГДА не используйте NormalizeDouble. Это ошибка. Его использование ВСЕГДА неправильно.
 
Thirteen:

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

Двойное значение от брокера может быть где угодно от 1.2345750000000000 до 1.23458499999999999 и все еще считаться той же самой ценой 1.23458.

И все же ваша функция говорит, что 1.2345750000000000 НЕ является GToE 1.23458499999999999.

И все же ваша функция говорит, что 1.23458499999999999999 НЕ ЯВЛЯЕТСЯ LToE 1.2345750000000000.

Вы должны использовать точку/2 в сравнениях https://www.mql5.com/en/forum/136997/page3#780837.

 
ydrol:

Я сам немного поиграл с этим - пытался достичь приемлемого компромисса между читабельностью и производительностью.

Я считаю, что 0.0 - это особый случай, поэтому вы можете тестировать непосредственно с 0.0
 
WHRoeder:

Двойное значение от брокера может быть где угодно от 1,2345750000000000 до 1,234584999999999 и все равно считаться той же самой ценой 1,23458.

В целом я согласен. См. мои P1 и P2 в моем сообщении выше.

WHRoeder:

Тем не менее, ваша функция говорит, что 1.2345750000000000 НЕ является GToE 1.23458499999999999.

И все же ваша функция говорит, что 1.23458499999999999999 НЕ является LToE 1.234575000000000000.

Проблема возникает из-за того, как MT4/MQL хранит значения с плавающей точкой в переменных. Например:

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

выводит эти две переменные в журнал/журнал:

Сравнение цен тест #1

Как вы можете видеть, p2 больше не является 1.23458499999999999, а вместо этого становится 1.23458500 - из-за, я полагаю, округления. Именно по этой причине моя функция говорит, что p1 не равно p2; и как вы можете видеть в приведенном ниже коде, ваш код также предполагает то же самое - то есть, что p1 не равно p2 и что p1 не равно 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.);

Сравнение цен тест #2

Вы должны использовать точку/2 в сравнениях

Существует вероятность того, что Point/2 - это слишком маленькое максимальное отклонение. Например:

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

Сравнение цен тест #3

Если предполагается, что 1,234575 равно 1,234580, то почему #2 показывает NEQ? Более того, если мы предположим, что 1.23458 - это цена, которая может означать цену у брокера, находящуюся в любом диапазоне от 1.2345750000000000 до 1.234584999999999, то почему #1 должен показывать NEQ? Разве они не должны быть равны, если у них одна и та же ценовая точка (отсюда моя предпосылка №2 в моем сообщении выше)?

 

@Thirteen,


В вашем коде вы рассматриваете преднамеренные ошибки округления из-за логики приложения, а не непреднамеренные ошибки округления из-за ошибок плавающей точки, отсюда и разница:

Два типа "округления":

a) ошибки внутреннего округления из-за двоичных дробей в формате IEEE. - Эти числа должны быть абсолютно одинаковыми, но это не так из-за двоичного представления десятичных дробей. Они округляются за счет MQ4-представления десятичных дробей.

б) Явное округление до некоторого числа или десятичных знаков. (например, при печати или отправке цен брокеру). - Эти значения на самом деле не должны быть одинаковыми, вместо этого они округляются логикой приложения для чьего-то удобства.

На самом деле это не является ошибкой. Ошибки, связанные исключительно с представлением с плавающей запятой, вряд ли будут настолько большими (если только не вычислять серию плохо). Но вы можете захотеть сделать такой тип сравнения в вашем приложении в соответствии с вашей собственной логикой.


Внутренние ошибки округления[a] обычно очень малы (на порядки меньше, чем Point) и непреднамеренны. Приложение не в состоянии округлить эти числа до точного значения, используя тип данных double .

Явные различия в округлении[b] являются преднамеренными и намного больше (+/- 0.5 пункта). (в данном случае). Поэтому два числа, округленные логикой приложения до одного и того же значения, могут изначально отличаться почти на целый балл.


В идеале я бы сначала округлил числа [b] (только если округление необходимо) , а затем сравнил их [a], в этот момент ошибка очень мала из-за ограничений удвоения. (например, < 0.0000001).

Но в вашем коде сравнение выполняется перед округлением, и в этом случае вам приходится иметь дело с гораздо большими возможными различиями. Однако округление требуется не всегда. Я бы использовал его только при отправке цен брокеру.


Подумайте об этом с другой стороны (Если бы MQ4 использовал двоично-кодированную десятичную систему счисления - которая позволяет точно представлять десятичные дроби - тогда все вопросы относительно Price != Price исчезли бы,

Но вам все равно придется округлять и сравнивать числа в вашем приложении до ближайшей точки для определенных операций. (В основном это функции OrderXXX).


>> "если мы предположим, что 1.23458 - это цена, то это может означать цену у брокера, которая находится в диапазоне от 1.2345750000000000 до 1.23458499999999999".

Я могу ошибаться (не знаю, как работают брокеры), но я думаю, что цена от брокера 1.23458 - именно такая. Особенно при размере лота $100,000 и больше. В противном случае можно заработать много денег (брокеру), используя разницу в опубликованных ценах.

Я понимаю, что на самом деле округлять нужно только при отправке брокеру, а не по всему приложению. В этом случае сравнения для небольшой погрешности должно быть достаточно.

Неточность плавающей точки отделена от округления для брокерских цен. Но если вы хотите иметь дело с ними обоими одновременно, я думаю, это личное предпочтение (хотя это может запутать?).

 

Вот моя полная версия, (надеюсь, без ошибок)...

Это обеспечивает 6 функций:

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


Рациональным является сохранение читабельности кода (IMO) и снижение вероятности ошибок при вводе, без слишком большого падения производительности.

Для всех намерений и целей эти функции должны быть настолько быстрыми, насколько это может быть сделано с помощью пользовательских функций MQ4,

(для сравнения производительности с MathAbs(a-b) < HalfPoint смотрите https://www.mql5.com/en/forum/136997/page5#822505, хотя в реальном советнике (в отличие от бенчмарка) я подозреваю, что разница незначительна.


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:

Вот моя полная версия (надеюсь, без ошибок)...

...

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

Часто цитируемая предпосылка такова:

  • Двойное значение от брокера может быть где угодно от 1.2345750000000000 до 1.234584999999999 и все равно считаться той же самой ценой 1.23458.

Учитывая эту предпосылку и используя ваш код в качестве фона, не могли бы вы объяснить мне, почему вы говорите, что (a) 1.234576 и 1.234584 считаются не равными, (b) 1.234577 и 1.234583 считаются не равными, но (c) 1.234578 и 1.234582 считаются равными? Почему (и каким образом) пример (b) является менее равным, чем пример (c)?

Как я уже говорил выше, я считаю все эти цены равными, потому что все они имеют одну и ту же ценовую точку - а именно 1,23458. Этот пример иллюстрирует, почему я считаю (и говорил об этом выше), что Point/2 может быть слишком маленьким максимальным отклонением.

 

@Thirteen, мой ответ на ваши наблюдения остается тем же 3 постами выше https://www.mql5.com/en/forum/136997/page5#822672 ссылка. Я повторю ту часть, которая может привести к озарению в понимании моей точки зрения: (с небольшими изменениями и добавленными акцентами)

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 )

но вам все равно придется округлять и сравнивать числа в вашем приложении до ближайшей точки для определенных операций. (В основном это функции OrderXXX).


Это зависит от того, как вы пишете код, и хотите ли вы различать округление в приложении (когда два разных числа концептуально/логически рассматриваются как одно и то же для простоты/удобства),

и ошибками с плавающей запятой. Здесь нет правильного и неправильного, но я думаю, что один подход более запутанный, чем другой.....


Кроме того, я лично немного скептически отношусь к нецитируемой предпосылке (но могу поправить!), снова упомянутой в предыдущем сообщении.

Причина обращения: