Przejdź do treści
MQL5 · MetaTrader 5 · po polsku
kod odczarowany

MQL5 / Kurs / Lekcja 2 z 7

Ceny, punkty i typ double

Dlaczego nie wolno porównywać cen operatorem równości, czym różni się punkt od pipsa i kiedy naprawdę potrzebujesz NormalizeDouble.

4 fragmenty kodu z tej strony przeszły przez kompilator MetaEditor build 6090, 2026-08-05.

Cena w MQL5 to double — liczba zmiennoprzecinkowa podwójnej precyzji. Wynika z tego kilka rzeczy, które trzeba wiedzieć, zanim napisze się pierwszy warunek.

Nigdy nie porównuj cen przez ==

double przechowuje wartości w systemie dwójkowym, a większości ułamków dziesiętnych nie da się w nim zapisać dokładnie. Wartość, która na ekranie wygląda jak 1.09310, w pamięci bywa 1.0930999999999999.

Tak nie działa
if(tick.bid == 1.09310)     // prawie zawsze fałsz
   Print("trafione");

Poprawne porównanie robi się przez różnicę i tolerancję. Naturalną tolerancją na rynku jest połowa punktu:

Tak jest poprawnie
if(MathAbs(tick.bid - 1.09310) < _Point / 2)
   Print("trafione");

Punkt to nie pips

_Point to najmniejsza zmiana ceny danego instrumentu — dla pięciocyfrowego EURUSD jest to 0.00001. Pips w potocznym rozumieniu to dziesięć razy więcej, 0.0001. Na rachunkach czterocyfrowych oba pojęcia się pokrywają, na pięciocyfrowych nie, i dlatego kod przepisany ze starego forum stawia stop loss dziesięć razy bliżej, niż autor zamierzał.

Przeliczanie pipsów na punkty
//--- ile punktów przypada na jednego pipsa dla tego symbolu
int PunktowNaPipsa()
  {
   return (_Digits == 3 || _Digits == 5) ? 10 : 1;
  }

//--- stop loss 20 pipsów poniżej ceny bid
double sl = tick.bid - 20 * PunktowNaPipsa() * _Point;

_Point, _Digits i ich pełne odpowiedniki

_Point i _Digits odnoszą się do symbolu bieżącego wykresu. Jeśli twój kod dotyka innego instrumentu, musisz zapytać o jego własne wartości:

double punkt  = SymbolInfoDouble("GBPUSD", SYMBOL_POINT);
int    cyfry  = (int)SymbolInfoInteger("GBPUSD", SYMBOL_DIGITS);

Użycie _Point przy pracy na wielu symbolach to błąd, który nie daje żadnego komunikatu — po prostu wyliczenia są ciche i złe.

NormalizeDouble — kiedy naprawdę

NormalizeDouble(wartosc, cyfry) zaokrągla liczbę do zadanej liczby miejsc po przecinku. Panuje przekonanie, że trzeba nim owijać wszystko. Nieprawda — jest potrzebny dokładnie w dwóch miejscach:

  1. Przy cenach wysyłanych w zleceniu, żeby serwer nie odrzucił zapytania z powodu nadmiarowych miejsc dziesiętnych.
  2. Przy wolumenie, który dodatkowo musi być wielokrotnością kroku wolumenu.
Zaokrąglanie wolumenu do kroku instrumentu
double DopasujWolumen(string symbol, double wolumen)
  {
   double minimum = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maksimum = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double krok = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);

   wolumen = MathRound(wolumen / krok) * krok;
   wolumen = MathMax(minimum, MathMin(maksimum, wolumen));

   //--- liczba miejsc dziesiętnych wynikająca z kroku, np. 0.01 -> 2
   int cyfry = (int)MathRound(-MathLog(krok) / MathLog(10.0));
   return NormalizeDouble(wolumen, MathMax(cyfry, 0));
  }

Wolumen niezgodny z krokiem instrumentu to najczęstsza przyczyna kodu zwrotnego 10014, a cena z nadmiarem miejsc dziesiętnych potrafi skończyć się 10015.

Kiedy to nie wystarcza

Zaokrąglanie i tolerancja rozwiązują większość przypadków, ale nie wszystkie. Głębiej o tym, dlaczego zlecenie oczekujące potrafi się nie aktywować mimo przebicia ceny, piszemy w iluzji dokładności.

Zadanie

Napisz funkcję bool CenyRowne(double a, double b, string symbol), która porówna dwie ceny z tolerancją równą połowie punktu danego symbolu. Sprawdź ją na parze, na której _Digits wynosi 5, i na takiej, gdzie wynosi 3 — na przykład na USDJPY.