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

MQL5 / Przepisy

Trailing stop, który nie wysyła zapytania przy każdym ticku

Gotowa funkcja podciągająca stop lossa z progiem aktywacji, minimalnym krokiem i kontrolą dystansu wymaganego przez brokera.

1 fragment kodu z tej strony przeszedł przez kompilator MetaEditor build 6090, 2026-08-05.

Naiwny trailing stop wygląda tak: przy każdym ticku policz nowy poziom i wyślij modyfikację. Na rachunku demo działa. Na rachunku rzeczywistym dostajesz odrzucone zapytania za zbyt częste żądania, a w dzienniku setki wpisów na minutę.

Cztery warunki, które muszą być spełnione

Zanim wyślesz modyfikację, sprawdź po kolei:

  1. Próg aktywacji. Zysk musi przekroczyć zadaną liczbę punktów. Bez tego stop podciąga się od pierwszej sekundy i wyrzuca z rynku na szumie.
  2. Minimalny krok. Nowy poziom musi być lepszy od obecnego o co najmniej kilka punktów. To jedyny warunek, który realnie ogranicza liczbę zapytań.
  3. Dystans wymagany przez brokera. SYMBOL_TRADE_STOPS_LEVEL, a gdy broker podaje tam zero — margines liczony ze spreadu.
  4. Strefa zamrożenia. SYMBOL_TRADE_FREEZE_LEVEL blokuje modyfikację blisko ceny. Zignorowanie tego daje 10016 w kółko.

Kod

MQL5\Include\TrailingStop.mqh
//+------------------------------------------------------------------+
//|                                             TrailingStop.mqh     |
//|  Podciąganie stop lossa bez wysyłania zapytania przy każdym ticku |
//|  https://finanseodczarowane.pl/mql5/przepisy/trailing-stop/        |
//|  Licencja MIT                                                     |
//+------------------------------------------------------------------+
#property copyright "finanseodczarowane.pl/kod"

//+------------------------------------------------------------------+
//| Przesuwa stop lossa pozycji o podanym bilecie, jeśli:             |
//|  - zysk przekroczył próg aktywacji,                               |
//|  - nowy poziom jest korzystniejszy od obecnego o co najmniej      |
//|    minimalny krok (dzięki temu nie wysyłamy zapytania co tick),   |
//|  - nowy poziom mieści się w dozwolonym dystansie od ceny.         |
//| Zwraca true tylko wtedy, gdy stop faktycznie zmieniono.           |
//+------------------------------------------------------------------+
bool PodciagnijStop(const ulong bilet,
                    const int dystansPunktow,
                    const int aktywacjaPunktow,
                    const int minimalnyKrokPunktow)
  {
   if(!PositionSelectByTicket(bilet))
      return false;

   const string symbol = PositionGetString(POSITION_SYMBOL);
   const long   typ    = PositionGetInteger(POSITION_TYPE);
   const double otwarcie = PositionGetDouble(POSITION_PRICE_OPEN);
   const double obecnySL = PositionGetDouble(POSITION_SL);
   const double tp       = PositionGetDouble(POSITION_TP);

   MqlTick tick;
   if(!SymbolInfoTick(symbol, tick))
      return false;

   const int    cyfry = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
   const double punkt = SymbolInfoDouble(symbol, SYMBOL_POINT);
   const long   stopy = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
   const long   zamrozenie = SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL);

   //--- minimalny dozwolony odstęp od ceny; przy zerze u brokera
   //--- bierzemy dwukrotność bieżącego spreadu jako margines
   const long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
   const double odstep = MathMax((double)stopy, spread * 2.0) * punkt;

   double nowySL = 0.0;

   if(typ == POSITION_TYPE_BUY)
     {
      if(tick.bid - otwarcie < aktywacjaPunktow * punkt)
         return false;                       // za mały zysk, nie ruszamy

      nowySL = tick.bid - dystansPunktow * punkt;

      if(nowySL > tick.bid - odstep)          // za blisko ceny
         nowySL = tick.bid - odstep;
      if(nowySL <= otwarcie)                  // nie schodzimy poniżej wejścia
         return false;
      if(obecnySL > 0 && nowySL - obecnySL < minimalnyKrokPunktow * punkt)
         return false;                        // zmiana zbyt drobna
      if(MathAbs(tick.bid - obecnySL) < zamrozenie * punkt)
         return false;                        // strefa zamrożenia
     }
   else if(typ == POSITION_TYPE_SELL)
     {
      if(otwarcie - tick.ask < aktywacjaPunktow * punkt)
         return false;

      nowySL = tick.ask + dystansPunktow * punkt;

      if(nowySL < tick.ask + odstep)
         nowySL = tick.ask + odstep;
      if(nowySL >= otwarcie)
         return false;
      if(obecnySL > 0 && obecnySL - nowySL < minimalnyKrokPunktow * punkt)
         return false;
      if(MathAbs(obecnySL - tick.ask) < zamrozenie * punkt)
         return false;
     }
   else
      return false;

   nowySL = NormalizeDouble(nowySL, cyfry);
   if(MathAbs(nowySL - obecnySL) < punkt / 2.0)
      return false;                           // nic się nie zmienia

   MqlTradeRequest zapytanie = {};
   MqlTradeResult  wynik = {};

   zapytanie.action   = TRADE_ACTION_SLTP;
   zapytanie.position = bilet;
   zapytanie.symbol   = symbol;
   zapytanie.sl       = nowySL;
   zapytanie.tp       = NormalizeDouble(tp, cyfry);

   if(!OrderSend(zapytanie, wynik))
     {
      PrintFormat("Modyfikacja stopa nie powiodła się: błąd %d, retcode %d",
                  GetLastError(), wynik.retcode);
      return false;
     }

   if(wynik.retcode != TRADE_RETCODE_DONE)
     {
      PrintFormat("Serwer odrzucił modyfikację: retcode %d (%s)",
                  wynik.retcode, wynik.comment);
      return false;
     }

   PrintFormat("Stop pozycji %I64u przesunięty na %.*f", bilet, cyfry, nowySL);
   return true;
  }

Pobierz plik

Użycie w ekspercie

Wywołanie raz na tick, dla własnych pozycji
#include <TrailingStop.mqh>

input int Dystans   = 300;   // punktów za ceną
input int Aktywacja = 200;   // punktów zysku przed startem
input int Krok      = 50;    // minimalna zmiana, żeby wysłać modyfikację
input ulong Magic   = 12345;

void OnTick()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong bilet = PositionGetTicket(i);
      if(bilet == 0 || !PositionSelectByTicket(bilet))
         continue;
      if(PositionGetInteger(POSITION_MAGIC) != (long)Magic)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;

      PodciagnijStop(bilet, Dystans, Aktywacja, Krok);
     }
  }

Trailing stop to jedyna część logiki, której nie ograniczamy do nowej świecy — cena może uciec w środku okresu. Ograniczenie liczby zapytań załatwia tu minimalny krok, nie filtr czasu.