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.
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:
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ł.
//--- 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:
- Przy cenach wysyłanych w zleceniu, żeby serwer nie odrzucił zapytania z powodu nadmiarowych miejsc dziesiętnych.
- Przy wolumenie, który dodatkowo musi być wielokrotnością kroku wolumenu.
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.