How to handle broker freeze zones in MQL5 Expert Advisors

 
@Maxymilian Sławomir Banaszek  Radzenie sobie ze strefami zamrożenia brokerów w XAUUSD Expert Advisors – praktyczny przewodnik

Kategoria: Eksperci / Poziom zarządzania handlem: Średniozaawansowany / Zaawansowany Skupienie symboli: XAUUSD (dotyczy dowolnego instrumentu zmiennego)


Wprowadzenie

Jeśli kiedykolwiek prowadziłeś Gold EA przez MQL5 Strategy Tester i widziałeś to w logu:

failed market sell 0.2 XAUUSD, close #60 buy 0.2 XAUUSD 1870.33 [Modification failed due to order or position being close to market] ...Repeated twenty times in a row - you've hit the broker freeze zone problem. failed market sell 0.2 XAUUSD, close #60 buy 0.2 XAUUSD 1870.33 [Modification failed due to order or position being close to market]

Ten artykuł dokładnie wyjaśnia, co to powoduje, dlaczego większość EA źle to obsługuje oraz jak to poprawić, aby Twoje EA przeszło weryfikację MQL5 Market bez spamu błędów.


Czym jest strefa zamrożenia?

Każdy broker definiuje poziom zamrożenia dla każdego symbolu – odległość (w punktach) od aktualnej ceny rynkowej, przy której nie można zmienić otwartej pozycji.

Możesz go przeczytać od:

Gdy cena rynkowa porusza się w tym zakresie stop loss lub take profit, broker blokuje pozycję. Od tego momentu: int freezeLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL);

  • Nie możesz modyfikować SL lub TP
  • Nie można zamknąć pozycji ręcznie
  • Każde , , lub połączenie zostanie odrzucone OrderSend CTrade::PositionClose CTrade::PositionModify

Broker zaraz wykona zatrzymanie. Każde zakłócenie ze strony EA skutkuje błędem.

Ma to na celu ochronę integralności zamówienia przy jednoczesnym szybkim wykonywaniu. Ale większość emocjonalistów tego nie bierze na oczy.


Dlaczego większość EAs tutaj nie kwalifikuje się

Typowa sekwencja zamykająca w EA wygląda tak:

// 1. Try to clear stops before closing trade.PositionModify(ticket, 0.0, 0.0); // 2. Send close trade.PositionClose(ticket); When a position is in the freeze zone, step 1 fails. The EA detects the error, tries, fails again—and if this works in a replay loop triggered on every tick, 20+ identical error lines appear within seconds. // 1. Try to clear stops before closing trade.PositionModify(ticket, 0.0, 0.0); // 2. Send close trade.PositionClose(ticket);

Nawet surowa transakcja nie pomaga – broker odrzuca wszystkie prośby, gdy pozycja jest zamrożona. CTrade::PositionClose OrderSend TRADE_ACTION_DEAL


Właściwe rozwiązanie

Nic nie rób.

Gdy pozycja znajduje się w strefie zamrożenia, broker jest o milisekundy od wykonania zatrzymania. Prawidłową czynnością jest wykrycie stanu zamrożenia i natychmiastowy powrót — bez modyfikacji, bez zamknięcia, bez ponownej próby.

Oto funkcja użyteczności, która sprawdza, czy ograniczniki pozycji znajdują się w odległości zamrożenia:

bool StopsInFreezeZone(const string sym, const ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; double pt = SymbolInfoDouble(sym, SYMBOL_POINT); if(pt <= 0) pt = _Point; int freezeLvl = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL); if(freezeLvl <= 0) return false; double freezeDist = freezeLvl * pt; double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double bid = SymbolInfoDouble(sym, SYMBOL_BID); double ask = SymbolInfoDouble(sym, SYMBOL_ASK); if(ptype == POSITION_TYPE_BUY) { if(sl > 0.0 && (bid - sl) < freezeDist) return true; if(tp > 0.0 && (tp - bid) < freezeDist) return true; } else { if(sl > 0.0 && (sl - ask) < freezeDist) return true; if(tp > 0.0 && (ask - tp) < freezeDist) return true; } return false; } Korzystanie z Garda bool StopsInFreezeZone(const string sym, const ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; double pt = SymbolInfoDouble(sym, SYMBOL_POINT); if(pt <= 0) pt = _Point; int freezeLvl = (int)SymbolInfoInteger(sym, SYMBOL_TRADE_FREEZE_LEVEL); if(freezeLvl <= 0) return false; double freezeDist = freezeLvl * pt; double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double bid = SymbolInfoDouble(sym, SYMBOL_BID); double ask = SymbolInfoDouble(sym, SYMBOL_ASK); if(ptype == POSITION_TYPE_BUY) { if(sl > 0.0 && (bid - sl) < freezeDist) return true; if(tp > 0.0 && (tp - bid) < freezeDist) return true; } else { if(sl > 0.0 && (sl - ask) < freezeDist) return true; if(tp > 0.0 && (ask - tp) < freezeDist) return true; } return false; }


W twojej bliskiej roli

Dodaj check-freeze na samym górze – przed jakimkolwiek lub call: OrderSend CTrade

bool ClosePosition(ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; string sym = PositionGetString(POSITION_SYMBOL); // If frozen — broker will execute SL. Do not interfere. if(StopsInFreezeZone(sym, ticket)) return false; CTrade trade; trade.SetDeviationInPoints(50); return trade.PositionClose(ticket); } In the modification/trace function bool ClosePosition(ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; string sym = PositionGetString(POSITION_SYMBOL); // If frozen — broker will execute SL. Do not interfere. if(StopsInFreezeZone(sym, ticket)) return false; CTrade trade; trade.SetDeviationInPoints(50); return trade.PositionClose(ticket); }

Ta sama zasada – pomij wszystkie modyfikacje po zamrożonym terminie:

void TrailStop(ulong ticket) { if(!PositionSelectByTicket(ticket)) return; // Never modify stops in freeze zone if(StopsInFreezeZone(_Symbol, ticket)) return; // ... your trail logic here } W zarządzaniu stanowiskami OnTick void TrailStop(ulong ticket) { if(!PositionSelectByTicket(ticket)) return; // Never modify stops in freeze zone if(StopsInFreezeZone(_Symbol, ticket)) return; // ... your trail logic here }

If you evaluate exit conditions on every tick, guard the entire block:

void OnTick() { // ... if(g_Ticket > 0 && PositionSelectByTicket(g_Ticket)) { // Skip all management when broker has the position locked if(!StopsInFreezeZone(_Symbol, g_Ticket)) { ManagePosition(); TrailStop(g_Ticket); } } } The ClearStops-Before-Close Pattern void OnTick() { // ... if(g_Ticket > 0 && PositionSelectByTicket(g_Ticket)) { // Skip all management when broker has the position locked if(!StopsInFreezeZone(_Symbol, g_Ticket)) { ManagePosition(); TrailStop(g_Ticket); } } }


Many EAs attempt to set SL and TP to zero before sending a close order. This is valid practice in normal conditions but must also be guarded:

bool ClearStopsBeforeClose(const string sym, const ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); if(sl == 0.0 && tp == 0.0) return true; // If the current SL/TP is already in the freeze zone, // modifying to zero will also be rejected. Skip it. if(StopsInFreezeZone(sym, ticket)) return false; CTrade tr; tr.SetDeviationInPoints(30); return tr.PositionModify(ticket, 0.0, 0.0); } Zapobieganie spamowi powtórek bool ClearStopsBeforeClose(const string sym, const ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); if(sl == 0.0 && tp == 0.0) return true; // If the current SL/TP is already in the freeze zone, // modifying to zero will also be rejected. Skip it. if(StopsInFreezeZone(sym, ticket)) return false; CTrade tr; tr.SetDeviationInPoints(30); return tr.PositionModify(ticket, 0.0, 0.0); }


Even with the freeze guard, a race condition can occur: the freeze check passes, the close is sent, the market moves into the freeze zone before the request reaches the broker, and the close is rejected. On the next tick the EA tries again.

Add a pending-close flag to prevent hammering on every tick:

bool g_ClosePending = false; datetime g_CloseLastAttempt = 0; // In your position management function: if(g_ClosePending) { if(TimeCurrent() - g_CloseLastAttempt < 3) return; g_ClosePending = false; } // After a failed close attempt: if(!ClosePosition(g_Ticket)) { g_ClosePending = true; g_CloseLastAttempt = TimeCurrent(); } // In CheckPosition() when position disappears: g_ClosePending = false; This limits retries to one attempt every 3 seconds regardless of tick frequency. bool g_ClosePending = false; datetime g_CloseLastAttempt = 0; // In your position management function: if(g_ClosePending) { if(TimeCurrent() - g_CloseLastAttempt < 3) return; g_ClosePending = false; } // After a failed close attempt: if(!ClosePosition(g_Ticket)) { g_ClosePending = true; g_CloseLastAttempt = TimeCurrent(); } // In CheckPosition() when position disappears: g_ClosePending = false;


Testing in the Strategy Tester

Zachowanie strefy zamrożenia jest w pełni symulowane w MT5 Strategy Tester na XAUUSD z rzeczywistymi danymi ticków. To naprawdę przydatne — jeśli twoje EA ma problem z zawieszaniem, tester będzie go niezawodnie wykrywał, dokładnie tak, jak pokazano we wstępie.

After applying the guards above, the journal should be completely clean of "Modification failed" errors. If errors persist, check whether you have any additional close or modify calls outside the main management function — timers, , or partial-close logic are common places where secondary calls are missed. OnTradeTransaction


Podsumowanie

Sytuacja Prawidłowa obsługa
SL/TP w strefie zamarzniętej Nic nie rób – wróć natychmiast
Próba zamknięcia podczas zamarzniętego stanu Return false – pozwól brokerowi wykonać SL
Próba modyfikacji podczas zamrożonego Pomiń - return false
Wyrównane zawody nie są równe z warunkami wyścigowymi Ustaw flagę oczekujących – spróbuj maksymalnie raz na 3 sekundy
Pętla powtórek z dziennika Protect przy każdym wyłączeniu lub modyfikacji StopsInFreezeZone()

Strefa zamrożenia to nie jest błąd ani dziwactwa brokera, którą trzeba omijać. To celowy mechanizm ochrony przed wykonaniem działań. Najczystszym zachowaniem EA jest szybkie wykrycie i ustąpienie miejsca na bok.


Podsumowanie

Błędy strefy zamrożenia są jednym z najczęstszych powodów, dla których Gold EAs generują szumowe czasopisma i nie przechodzą weryfikacji rynku MQL5. Rozwiązanie jest sprzeczne z intuicją — zamiast bardziej się starać, by zamknąć pozycję, całkowicie przestajesz próbować i pozwalasz maklerowi wykonać pracę.

Funkcje użyteczności i wzorce opisane w tym artykule są testowane produkcyjnie na XAUUSD i działają poprawnie zarówno w handlu na żywo, jak i w MT5 Strategy Tester.

Jeśli uznałeś to za pomocne lub masz pytania dotyczące konkretnych przypadków borderline — zwłaszcza dotyczących częściowych zamknięcia i interakcji ze strefą zamrażania — śmiało podziel się nimi w komentarzach poniżej.


Słowa kluczowe: MQL5, Expert Advisor, XAUUSD, Gold EA, freeze level, stop loss, zarządzanie pozycją, Strategy Tester, zarządzanie transakcjami, MQL5 Market