Wycieki uchwytów wskaźników
Uchwyt tworzony w OnTick wygląda niewinnie, a po godzinach pracy terminal zwalnia. Jak wykrywać i zamykać wycieki.
1 fragment kodu z tej strony przeszedł przez kompilator MetaEditor build 6090, 2026-08-05.
W architekturze MQL5 wskaźniki (iMA, iRSI, iCustom) nie są zwykłymi funkcjami matematycznymi zwracającymi wartość. Są one obiektami "pod maską" terminala. Wywołanie iMA(...) tworzy w pamięci terminala instancję wskaźnika, oblicza jego historię, a Tobie zwraca jedynie uchwyt (handle) – liczbowy identyfikator (int), przez który odpytujesz o wartości. Jest to koncepcja bliska programistom C/C++ operującym na plikach lub gniazdach sieciowych (file descriptors). Niestety, zarządzanie tymi uchwytami to koszmar wielu programistów MQL5.
Problematyczna funkcja OnDeinit
Częstym błędem jest tworzenie uchwytów dynamicznie lub wielokrotnie bez ich zwalniania. Każde wywołanie iMA alokuje zasoby. Jeśli utworzysz uchwyt wewnątrz OnTick(), w ciągu jednego dnia handlowego zablokujesz terabajty pamięci, a terminal ostatecznie ulegnie awarii (Out of Memory). Jednak błąd ten potrafi być bardzo subtelny.
Rozważmy scenariusz, w którym parametry wskaźnika zależą od zmiennej zewnętrznej, która może się zmieniać. Wielu programistów umieszcza tworzenie uchwytów w OnInit(). Ale co, jeśli użytkownik zmieni parametry EA na wykresie bez jego usuwania? Zostanie wywołana funkcja OnDeinit, a zaraz po niej OnInit. Jeśli nie zwolnisz starego uchwytu, stworzysz nowy – stary zostanie "osierocony" w pamięci terminala do momentu jego restartu.
Prawidłowe wzorce zarządzania uchwytami
Istnieją dwa główne podejścia: zarządzanie statyczne (zawsze tworzymy w OnInit i zwalniamy w OnDeinit) oraz dynamiczne (tworzymy i zwalniamy w locie, gdy parametry się zmieniają). Dynamiczne jest znacznie trudniejsze do poprawnego zaimplementowania, ponieważ MQL5 asynchronicznie w tle oblicza historię nowo utworzonego wskaźnika. Jeśli odpytasz go ułamek sekundy po utworzeniu, otrzymasz błąd lub pustą tablicę.
int maHandle = INVALID_HANDLE;
int OnInit() {
// Najpierw zwalniamy poprzedni uchwyt, jeśli istniał (bezpieczeństwo przy reinicie)
if(maHandle != INVALID_HANDLE) {
IndicatorRelease(maHandle);
maHandle = INVALID_HANDLE;
}
// Tworzymy nowy
maHandle = iMA(_Symbol, PERIOD_CURRENT, 14, 0, MODE_EMA, PRICE_CLOSE);
if(maHandle == INVALID_HANDLE) {
Print("Błąd tworzenia uchwytu MA! Kod błędu: ", GetLastError());
return(INIT_FAILED); // Zatrzymanie EA
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason) {
// Złota zasada: każdy iMA/iRSI/iCustom musi tu zostać zwolniony!
if(maHandle != INVALID_HANDLE) {
IndicatorRelease(maHandle);
maHandle = INVALID_HANDLE;
}
}
void OnTick() {
double maBuffer[];
ArraySetAsSeries(maBuffer, true);
// Kopiujemy najnowszą wartość. Zawsze sprawdzaj ilość skopiowanych danych!
int copied = CopyBuffer(maHandle, 0, 0, 2, maBuffer);
if(copied <= 0) {
// Wskazówka: Błąd 4002 lub 4401 oznacza, że wskaźnik nie jest jeszcze gotowy
// (historycznie nie przeliczony). EA musi to zignorować i spróbować w kolejnym ticku.
int err = GetLastError();
Print("Kopiowanie bufora nieudane. Błąd: ", err);
return;
}
// Dalsza logika...
Print("Wartość MA: ", maBuffer[0]);
}