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:
- 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.
- Minimalny krok. Nowy poziom musi być lepszy od obecnego o co najmniej kilka punktów. To jedyny warunek, który realnie ogranicza liczbę zapytań.
- Dystans wymagany przez brokera.
SYMBOL_TRADE_STOPS_LEVEL, a gdy broker podaje tam zero — margines liczony ze spreadu. - Strefa zamrożenia.
SYMBOL_TRADE_FREEZE_LEVELblokuje 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;
}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.