Architektura obsługi błędów
Dwa równoległe światy błędów: lokalne kody wykonania i kody zwrotne serwera. Lepkie błędy w GetLastError, ponawianie z odstępem i decyzja, kiedy się poddać.
3 fragmenty kodu z tej strony przeszły przez kompilator MetaEditor build 6090, 2026-08-05.
Większość programistów MQL5 traktuje obsługę błędów jako dodatek – parę instrukcji if rzucających Print("Błąd: ", GetLastError()). Podejście to jest akceptowalne w skryptach testowych, ale w profesjonalnym algorytmicznym tradingu (zwłaszcza HFT lub na kontach prop tradingu) brak rygorystycznej architektury obsługi błędów to gwarancja utraty kapitału. Zlecenia nie wysyłają się w próżni; podróżują przez warstwy terminala, sieci VPN, mosty płynności i serwery brokera. Na każdym z tych etapów może dojść do awarii. Zrozumienie, skąd pochodzi błąd i jak na niego zareagować, to cecha wyróżniająca kod produkcyjny od amatorskiego.
1. Dwa Równoległe Wszechświaty Błędów
W MQL5 błędy dzielą się fundamentalnie na dwie kategorie, rządzące się różnymi prawami fizyki. Pomylenie ich to najczęstsza przyczyna błędnej diagnozy:
- Błędy Środowiska (Runtime / Terminal Errors): Zdarzają się lokalnie. Obejmują błędy pamięci (np.
0xC0000005 Access Violation), błędne parametry funkcji wbudowanych, odwrócone indeksowanie tablic czy brak połączenia z serwerem handlowym. Odczytuje się je funkcjąGetLastError().
- Błędy Serwera (Trade Server Errors): Zdarzają się zdalnie. Terminal poprawnie sformułował żądanie, wysłał je przez sieć, ale serwer brokera je odrzucił (np. brak marży, zbyt bliski Stop Loss, rynek zamknięty). Zwracane są w strukturze
MqlTradeResultw poluretcode.
2. Dziedzictwo C: Pułapka "Lepkich" Błędów w GetLastError()
Funkcja GetLastError() w MQL5 to wrapper nawiązujący do standardu języka C (errno). Działa ona na zasadzie globalnej zmiennej wątku (thread-local storage). Problem architektoniczny polega na tym, że kod błędu nie resetuje się samoczynnie po udanej operacji. Jeśli w linijce 50 wystąpił błąd odczytu pliku, a w linijce 100 wystąpi błąd pobrania ceny, wywołanie GetLastError() w linijce 101 zwróci błąd z linijki 100. Ale jeśli pobranie ceny w linijce 100 zakończy się sukcesem, wywołanie GetLastError() w linijce 101 nadal zwróci stary błąd z linijki 50.
// ANTYPWZORZEC: Fałszywe alarmy
void OnTick() {
double sum = 0;
// Załóżmy, że tu wystąpił błąd (np. wskaźnik nie gotowy, błąd 4401)
double val = iCustom(_Symbol, 0, "MyIndi", 0, 0);
// Później w kodzie operacja udana (np. pobranie czasu)
datetime t = TimeCurrent();
// Sprawdzamy błąd operacji udanej...
int err = GetLastError();
if(err != 0) {
// TU WEJDZIEMY! Zwróci 4401 z poprzedniej operacji!
Print("Błąd TimeCurrent? To niemożliwe! Kod: ", err);
}
}
// WZORZEC: Rygorystyczne czyszczenie stosu
void SafeTick() {
ResetLastError(); // Złota zasada: Zawsze przed ryzykowną operacją
double val = iCustom(_Symbol, 0, "MyIndi", 0, 0);
int err = GetLastError();
if(err != 0) {
Print("Rzeczywisty błąd iCustom: ", err);
return;
}
// Dalszy kod
}3. Anatomia retcode: Co odpowiedział serwer?
Kiedy używasz OrderSend() lub biblioteki CTrade, terminal odsyła strukturę MqlTradeResult. Kluczowe jest pole retcode. Wartości z zakresu 10000-10049 to standardowe kody MQL5, powyżej 10050 to niestandardowe kody zwracane przez infrastrukturę brokera (np. odrzucenia przez algorytmy ryzyka).
Profesjonalny EA nie może zakładać, że brak błędu środowiskowego (GetLastError == 0) oznacza otwarcie pozycji. Poniżej przedstawiam klasyfikację kodów retcode pod kątem akcji, jaką należy podjąć:
Klasyfikacja taktyczna kodów serwera:
- 10009 (TRADE_RETCODE_DONE) oraz 10008 (TRADE_RETCODE_PLACED) - Sukces. Zlecenie zrealizowane lub umieszczone w arkuszu.
- 10004 (TRADE_RETCODE_REQUOTE), 10021 (TRADE_RETCODE_NO_MONEY), 10027 (TRADE_RETCODE_AUTOTRADING_DISABLED_BY_SERVER) - Błąd przejściowy (Retryable). Można spróbować ponowić zlecenie po krótkim czasie (zaktualizować cenę dla Requote).
- 10013 (TRADE_RETCODE_INVALID), 10014 (TRADE_RETCODE_INVALID_VOLUME), 10016 (TRADE_RETCODE_INVALID_STOPS) - Błąd logiczny (Fatal). Nigdy nie ponawiaj ślepo tego zlecenia! Zlecenie jest fundamentalnie źle skonstruowane.
- 10018 (TRADE_RETCODE_MARKET_CLOSED) - Błąd środowiskowy rynkowego. EA powinien przejść w stan "uśpienia" do otwarcia sesji.
4. Implementacja Architektury Retry (Ponowienia)
W środowisku sieciowym błędy typu Requote (10004) czy połączeniowe (np. GetLastError() zwracający 527 (ERR_TRADE_SEND_FAILED)) są nieuniknione. Ślepe wysyłanie zlecenia w pętli to gwarancja zablokania konta za spam serwera. Należy zaimplementować mechanizm wykładniczego wycofywania (Exponential Backoff).
bool SendOrderWithRetry(double volume, double price) {
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = volume;
request.type = ORDER_TYPE_BUY;
request.price = price;
request.deviation = 20;
int maxRetries = 3;
int delayMs = 100;
for(int attempt = 0; attempt < maxRetries; attempt++) {
ResetLastError();
bool sent = OrderSend(request, result);
// 1. Sprawdzamy błąd sieciowy/środowiskowy
if(!sent) {
int err = GetLastError();
Print("Próba ", attempt+1, " - Błąd sieciowy: ", err);
Sleep(delayMs);
delayMs *= 2; // Wykładnicze opóźnienie (100, 200, 400 ms)
continue;
}
// 2. Sprawdzamy odpowiedź serwera (retcode)
if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED) {
return true; // Sukces!
}
else if(result.retcode == TRADE_RETCODE_REQUOTE) {
Print("Requote. Aktualizuję cenę i ponawiam.");
request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Aktualizujemy cenę
Sleep(delayMs);
continue;
}
else {
// Błędy fatalne (10013, 10016 itp.) - nie ponawiamy, ostrzegamy usera
Print("Błąd krytyczny serwera: ", result.retcode, " - ", result.comment);
return false;
}
}
Print("Nie udało się wysłać zlecenia po ", maxRetries, " próbach.");
return false;
}5. Asynchroniczny Koszmar: Błędy w OnTradeTransaction
Jeśli używasz OrderSendAsync(), cała powyższa logika zmienia się diametralnie. Funkcja asynchroniczna wraca niemal natychmiast. Struktura result zawiera jedynie informację "przyjęto do realizacji" (TRADE_RETCODE_PLACED), a nie informację o faktycznym otwarciu/zamknięciu pozycji.
Rzeczywisty błąd (lub sukces) dotrze do Twojego EA ułamek sekundy później (lub kilka sekund, gdy sieć ma opóźnienia) za pośrednictwem funkcji OnTradeTransaction(). Wymaga to utrzymywania w pamięci tzw. maszyny stanów (State Machine) dla oczekujących zleceń.
ulong pendingTicket = 0;
void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) {
// Interesują nas tylko zlecenia, które wysłaliśmy
if(trans.order == pendingTicket) {
if(trans.type == TRADE_TRANSACTION_ORDER_DELETE) {
// Zlecenie usunięte z systemu (zrealizowane lub odrzucone)
if(trans.order_state == ORDER_STATE_FILLED) {
Print("Asynchroniczne zlecenie zrealizowane!");
pendingTicket = 0;
}
else if(trans.order_state == ORDER_STATE_REJECTED) {
// TUTAJ dociera asynchroniczny błąd serwera!
// result.retcode zawiera przyczynę odrzucenia.
Print("Zlecenie asynchroniczne ODRZUCONE! Kod: ", result.retcode);
pendingTicket = 0;
// Tu możemy podjąć decyzję o ponowieniu lub powiadomieniu użytkownika
}
}
}
}Podsumowanie
Obsługa błędów w MQL5 to nie dodatek, to rdzeń architektury. Podział na błędy środowiskowe (GetLastError) i serwerowe (retcode) wymusza podwójną weryfikację każdej operacji handlowej. Pamiętaj o "lepkich" błędach i używaj ResetLastError() jak tarczy. Implementuj logikę ponawiania (Retry) tylko dla błędów przejściowych, chroniąc serwer przed spamem, a na koniec – jeśli sięgasz po asynchroniczność – przygotuj się na zarządzanie chaosem zdarzeń w OnTradeTransaction. Kod, który przewiduje awarie sieci i odrzucenia brokera, to system, który przetrwa na koncie rzeczywistym.