Hello,
I am working on a trailing stop function and found a difference in behavior between two methods.
I use this logic for ATR trailing:
double atr = iATR(_Symbol, PERIOD_CURRENT, 14);
double distance = atr * 2.5;
For BUY: new_sl = SymbolInfoDouble(_Symbol, SYMBOL_BID) - distance;
For SELL: new_sl = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + distance;
The code works, but on XAUUSD the SL is modified too often and sometimes gets error [130] invalid stops when spread widens during high volatility.
My current modification check:
if(new_sl > sl_current + _Point*10)
Question:
1. How do you correctly handle invalid stops error for ATR trailing on Gold?
2. Should I use SYMBOL_BID / ASK or PositionGetDouble(POSITION_PRICE_CURRENT) for calculating new SL?
3. Is it better to check Freeze Level and Stops Level before modifying?
Here is my full function:
void CheckATR_TrailingStop(string symbol, double atr_multiplier=2.5, int atr_period=14)
{
double atr_buf[1];
if(CopyBuffer(iATR(symbol, PERIOD_CURRENT, atr_period), 0, 0, 1, atr_buf) <=0) return;
double atr = atr_buf[0];
// ... rest of logic
// I check stops level here
int stops_level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
}
Any advice on best practice for ATR trailing on volatile symbols would be appreciated.
Thanks.
- Best trailing stop management for XAUUSD H1?
- Invalid stops and modification failed errors in My EA on EURUSD H1 netting How to fix SL/TP issues?
- [Invalid Stops]- Print("Stop Level is: ", stops_level); returning 0
Adi Widiyapermadi:
Generally, I insert my normalized bid/ask related math directly into my order sending line of code. In this way, the current bid/ask doesn't get much time to move prior to sending an order.
Any advice on best practice for ATR trailing on volatile symbols would be appreciated.
Yes, check both levels before every modify. That removes most of the invalid stops errors. A few other things in the code:
- iATR() in MQL5 returns a handle, not a value, so double atr = iATR(...) in your first snippet is the handle number. Create the handle once in OnInit and only call CopyBuffer later. Calling iATR inside the trailing function every tick is also wasteful.
- For a buy, the SL triggers on Bid, so trail from Bid. For a sell, trail from Ask. POSITION_PRICE_CURRENT gives the same thing, but SymbolInfoTick() is fresher right before you send.
- Use the closed bar (shift 1) for ATR. Bar 0 changes every tick, so your distance keeps moving and you modify more often.
- Replace the fixed _Point*10 step with a step tied to ATR, e.g. 10–20% of it. On gold, 10 points is basically nothing.
- If SYMBOL_TRADE_STOPS_LEVEL is 0, the broker can still use a floating level. Using the current spread as the minimum distance is a safe fallback.
Rough version (buy side; mirror it for sells):
// Forum snippet #1 (MQL5): ATR trailing for a BUY position. Mirror it for sells.
#include <Trade\Trade.mqh>
CTrade trade;
int atrHandle = INVALID_HANDLE; // create in OnInit: iATR(_Symbol, PERIOD_CURRENT, 14)
int OnInit()
{
atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14);
return(atrHandle == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED);
}
void TrailBuy(ulong ticket, double mult)
{
if(!PositionSelectByTicket(ticket)) return;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) return; // ATR handle is for _Symbol
if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) return;
double atr[1];
if(CopyBuffer(atrHandle, 0, 1, 1, atr) != 1) return; // closed bar
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return;
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize <= 0) tickSize = _Point;
long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
long spread = (long)MathRound((tick.ask - tick.bid) / _Point);
double minDist = (double)MathMax(stops, spread) * _Point;
double curSL = PositionGetDouble(POSITION_SL);
double curTP = PositionGetDouble(POSITION_TP);
double newSL = MathFloor((tick.bid - atr[0] * mult) / tickSize) * tickSize; // round away from price
newSL = NormalizeDouble(newSL, _Digits);
if(tick.bid - newSL < minDist) return; // stops level
if(curSL > 0 && tick.bid - curSL <= freeze * _Point) return; // SL inside freeze zone
if(curTP > 0 && curTP - tick.bid <= freeze * _Point) return; // TP inside freeze zone
if(curSL > 0 && newSL - curSL < atr[0] * 0.15) return; // step filter (only moves up)
if(!trade.PositionModify(ticket, newSL, curTP))
PrintFormat("modify failed: %u %s", trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
The freeze check matters when SL or TP is already close to price, which tends to be exactly when gold spikes. The code also skips the modify when the TP is inside the freeze level, because a modify sends both values. If you still get 10016 after this, print bid, ask, newSL, stops and freeze on the failed call. That usually shows which rule it broke.
You are missing trading opportunities:
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
Registration
Log in
You agree to website policy and terms of use
If you do not have an account, please register