A Trailing Stop Loss (TSL) is a pivotal risk management tool for algorithmic traders. In MQL5, implementing a TSL allows for dynamic protection of profits while letting successful trades run. This article explores its mechanics, implementation, and advanced strategies within the MetaTrader 5 environment.
What is a Trailing Stop Loss?
A Trailing Stop Loss is an order that automatically adjusts the Stop Loss level of an open position as the price moves favorably. For a buy position, the TSL moves upwards if the market price rises but remains stationary if the price falls. Conversely, for a sell position, it moves downwards as the market price drops but stays fixed if the price rallies. The key is that it only moves in one direction – the direction that locks in more profit.
Benefits of Using Trailing Stop Loss in MQL5
Implementing TSL in your MQL5 Expert Advisors (EAs) or scripts offers several advantages:
- Profit Maximization: Allows traders to capture a larger portion of a trend by not prematurely exiting a winning trade.
- Risk Reduction: Secures unrealized gains as the trade progresses, turning paper profits into locked-in profits once the TSL is surpassed by the open price.
- Emotional Discipline: Automates the decision-making process of when to move the stop loss, removing emotional biases like greed or fear.
- Customization: MQL5 provides the flexibility to create highly tailored TSL mechanisms based on various criteria, far beyond the basic server-side trailing stops offered by some brokers.
When to Use Trailing Stop Loss
Trailing Stop Loss is most effective under specific market conditions:
- Trending Markets: Ideal for markets exhibiting clear directional momentum. The TSL follows the trend, protecting accumulating profits.
- Breakout Strategies: After a price breaks out of a consolidation pattern, a TSL can help secure gains as the price moves strongly in the breakout direction.
- Volatility: While beneficial, the trailing distance needs careful adjustment in volatile markets to avoid premature stop-outs due to whipsaws. ATR-based TSLs are particularly useful here.
It’s generally less suitable for ranging markets where price oscillates within a tight band, as this can lead to frequent, unnecessary exits.
Implementing Trailing Stop Loss in MQL5: Code Examples
MQL5 offers robust tools to implement client-side trailing stops. The core logic involves iterating through open positions, calculating the new potential Stop Loss, and modifying the order if conditions are met. We’ll use the CTrade class for trade operations.
Prerequisites for Code Examples:
Include the Trade library:
#include <Trade\Trade.mqh>
CTrade trade;
Define input parameters for your EA or script:
input int TrailingStopPips = 50; // Trailing stop distance in pips
input int TrailingStepPips = 1; // Minimum price movement to trigger trail adjustment
Basic Trailing Stop Loss Implementation
This example trails the stop loss by a fixed number of pips. The trailing stop is adjusted within the OnTick() function of an EA.
void OnTick()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber || PositionGetString(POSITION_SYMBOL) != _Symbol)
continue; // Check magic number and symbol
double currentPrice = 0.0;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double newSL = currentSL;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// Calculate new SL only if current price is above open price + trailing stop distance
if(currentPrice > openPrice + TrailingStopPips * point)
{
double potentialSL = currentPrice - TrailingStopPips * point;
// Ensure SL is at least 'TrailingStepPips' away from previous SL or open price if no SL
if( (currentSL == 0 && potentialSL > openPrice + TrailingStepPips * point) ||
(currentSL != 0 && potentialSL > currentSL + TrailingStepPips * point) )
{
newSL = potentialSL;
}
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
// Calculate new SL only if current price is below open price - trailing stop distance
if(currentPrice < openPrice - TrailingStopPips * point)
{
double potentialSL = currentPrice + TrailingStopPips * point;
if( (currentSL == 0 && potentialSL < openPrice - TrailingStepPips * point) ||
(currentSL != 0 && potentialSL < currentSL - TrailingStepPips * point) )
{
newSL = potentialSL;
}
}
}
// Normalize and check against current SL and stops level
newSL = NormalizeDouble(newSL, digits);
if(newSL != currentSL) // If new SL is different from current SL
{
// For BUY: newSL must be > openPrice and > currentPrice - stopsLevel*point
// For SELL: newSL must be < openPrice and < currentPrice + stopsLevel*point
bool conditionBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
newSL > openPrice &&
newSL < currentPrice - stopsLevel * point);
bool conditionSell = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
newSL < openPrice &&
newSL > currentPrice + stopsLevel * point);
if(conditionBuy || conditionSell)
{
if(!trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP)))
{
PrintFormat("Error modifying position %d SL: %d", ticket, GetLastError());
}
}
}
}
}
}
Note on MQL4: In MQL4, you would use OrdersTotal(), loop with OrderSelect(), and use OrderModify(). The logic for SL calculation remains similar, but trade execution functions differ.
Trailing Stop Loss Based on ATR (Average True Range)
ATR adapts the trailing distance to market volatility. A common approach is to set the TSL at a multiple of the ATR value away from the current price.
input int ATRPeriod = 14;
input double ATRMultiplier = 2.0;
// ... inside OnTick() and position loop ...
double atrValue[1];
if(CopyBuffer(iATR(_Symbol, PERIOD_CURRENT, ATRPeriod), 0, 1, 1, atrValue) < 1)
{
Print("Error copying ATR buffer");
return;
}
double trailDistance = atrValue[0] * ATRMultiplier;
// Modify the previous basic TSL logic:
// Replace 'TrailingStopPips * point' with 'trailDistance'
// Example for BUY position potential SL:
// double potentialSL = currentPrice - trailDistance;
// The rest of the modification logic (checking against current SL, stopsLevel, etc.) remains crucial.
This makes the trailing stop wider in volatile markets and tighter when volatility is low.
Trailing Stop Loss Based on Price Action
A common price action TSL involves trailing the stop below the low of the last N bars for a buy position, or above the high of the last N bars for a sell position.
input int PriceActionLookback = 3; // Number of previous bars to look back
// ... inside OnTick() and position loop ...
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double lowestLow[1];
// Get the lowest low of the last 'PriceActionLookback' completed bars
if(CopyLow(_Symbol, PERIOD_CURRENT, 1, PriceActionLookback, lowestLow) < PriceActionLookback)
{
// If not enough history, find min over available bars or skip
// For simplicity, we assume enough bars or use ArrayMinimum on available data.
// Here we take the low of the most recent fully formed bar as an example.
if(CopyLow(_Symbol, PERIOD_CURRENT, 1, 1, lowestLow) < 1) return;
}
// More robust: Find the minimum of the 'PriceActionLookback' lows
// MqlRates rates[]; ArraySetAsSeries(rates, true);
// if(CopyRates(_Symbol, PERIOD_CURRENT, 1, PriceActionLookback, rates) < PriceActionLookback) return;
// double minLow = rates[0].low;
// for(int k=1; k<PriceActionLookback; k++) minLow = MathMin(minLow, rates[k].low);
// potentialSL = minLow;
// Simplified for example: Use low of bar index 1 (previous closed bar)
if(CopyLow(_Symbol, PERIOD_CURRENT, 1, 1, lowestLow) < 1) return;
double potentialSL = NormalizeDouble(lowestLow[0] - SymbolInfoDouble(_Symbol, SYMBOL_POINT) * AdjustPipOffset, digits); // AdjustPipOffset can be a small buffer
// Apply if potentialSL is more favorable and respects rules
if(potentialSL > currentSL && potentialSL > openPrice && potentialSL < currentPrice - stopsLevel * point)
{
newSL = potentialSL;
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double highestHigh[1];
// Similar logic for highest high for sell positions
if(CopyHigh(_Symbol, PERIOD_CURRENT, 1, 1, highestHigh) < 1) return;
double potentialSL = NormalizeDouble(highestHigh[0] + SymbolInfoDouble(_Symbol, SYMBOL_POINT) * AdjustPipOffset, digits);
if(potentialSL < currentSL && potentialSL < openPrice && potentialSL > currentPrice + stopsLevel * point)
{
newSL = potentialSL;
}
}
// ... then proceed with the trade.PositionModify() logic ...
Note: The AdjustPipOffset (e.g., 1-5 pips) is often added to place the SL slightly beyond the swing point.
Customizing Your MQL5 Trailing Stop Loss
Adjusting the Trailing Step
The “trailing step” dictates how frequently the TSL is updated. Instead of moving the SL with every pip of favorable price movement, a step can be introduced. For instance, a 50-pip TSL with a 10-pip step means the SL only moves after the price has secured an additional 10 pips of profit beyond the current TSL placement.
To implement this, modify the condition for newSL calculation:
// Inside the basic TSL logic, before setting newSL
// For BUY:
// if(potentialSL > currentSL + TrailingStepPips * point)
// {
// newSL = potentialSL;
// }
// For SELL:
// if(potentialSL < currentSL - TrailingStepPips * point)
// {
// newSL = potentialSL;
// }
This prevents overly frequent OrderModify calls.
Setting a Minimum Profit Lock (Breakeven Stop)
A common strategy is to move the SL to breakeven (or slightly in profit) once the trade achieves a certain profit target. This can be the first stage of a trailing stop.
input int BreakevenPips = 30; // Profit pips to trigger breakeven
input int BreakevenLockPips = 2; // Pips above/below open price to lock in
// ... inside OnTick() and position loop, before regular trailing logic ...
if(currentSL != openPrice + BreakevenLockPips * point && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
if(currentPrice >= openPrice + BreakevenPips * point)
{
double breakEvenSL = openPrice + BreakevenLockPips * point;
breakEvenSL = NormalizeDouble(breakEvenSL, digits);
if(breakEvenSL < currentPrice - stopsLevel * point) // Ensure valid SL placement
{
if(!trade.PositionModify(ticket, breakEvenSL, PositionGetDouble(POSITION_TP)))
{
PrintFormat("Error modifying position %d to breakeven: %d", ticket, GetLastError());
}
else
{
// Potentially skip further trailing for this tick, or let regular trailing take over
// return;
}
}
}
}
// Similar logic for SELL positions
Combining Trailing Stop Loss with Other Indicators
TSLs can be guided by technical indicators:
- Moving Averages: Trail the SL along a MA (e.g., EMA20). For a buy, SL =
iMA(NULL, 0, PeriodMA, 0, MODE_EMA, PRICE_CLOSE, 1) - OffsetPips * point. - Parabolic SAR: The PSAR itself acts as a trailing stop. Use
iSAR()values directly. - Donchian Channels / Bollinger Bands: Trail along the lower band for buys, upper band for sells.
The implementation involves fetching the indicator value for the relevant bar (usually the previous closed bar, index 1) and using it to calculate potentialSL.
Advanced Trailing Stop Loss Strategies in MQL5
Dynamic Trailing Stop Loss Adjustment
This involves altering the trailing distance itself based on market conditions, not just the SL price. For instance:
- Volatility-based distance: Increase ATR multiplier if volatility (e.g., measured by ATR value or standard deviation) increases significantly.
- Momentum-based distance: If momentum (e.g., from RSI or Momentum indicator) is strong, use a tighter trail; if momentum wanes, widen it.
This requires additional logic to assess market regime and adjustTrailingStopPipsorATRMultiplierdynamically.
Trailing Stop Loss for Different Market Conditions
Effective TSLs adapt. An EA might switch TSL strategies:
- Trending: Use ATR-based or price-action TSL.
- Ranging: Use a very wide TSL, a fixed (non-trailing) catastrophic stop, or disable TSL entirely to avoid whipsaws. Market regime detection (e.g., using ADX) becomes crucial.
Using Trailing Stop Loss with Expert Advisors (EAs)
Integrating TSL into an EA is standard practice:
- Event Handling: Place TSL logic within
OnTick()for frequent checks orOnTimer()for less frequent updates to reduce load. - Magic Number: Crucially, ensure the TSL logic only manages trades opened by the EA itself, by filtering positions based on their
POSITION_MAGIC. - Client-Side vs. Server-Side: MQL5 TSLs are client-side, meaning MetaTrader 5 must be running for them to function. Server-side TSLs (if offered by the broker) work even if MT5 is closed but offer less customization.
- Order Management Conflicts: Ensure TSL logic doesn’t interfere with other order management functions like take profit handling or closing conditions.
Troubleshooting and Best Practices
Common Mistakes When Implementing Trailing Stop Loss
- Error 130 (ERRINVALIDSTOPS): Setting SL too close to the current market price (violating
SYMBOL_TRADE_STOPS_LEVEL) or on the wrong side of the open price. Always normalize prices and check against this level.
mq5
// Before calling PositionModify for a BUY:
// if (newSL >= SymbolInfoDouble(_Symbol, SYMBOL_BID) - SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point)
// // SL is too close
- Error 148 (ERRTRADETOOMANYORDERS) or broker limitations: Modifying orders too frequently. Implement a step or check
TimeCurrent() - lastModificationTime > minInterval. - Incorrect Pip/Point Calculation: Ensure correct usage of
_PointorSymbolInfoDouble(_Symbol, SYMBOL_POINT)andSymbolInfoInteger(_Symbol, SYMBOL_DIGITS)for price normalization.NormalizeDouble()is your friend. - Modifying Non-Existent or Pending Orders: Always ensure
PositionSelectByTicket()is successful and the position is an open market order. - Forgetting
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL): During very fast markets, or if an order is too close to market, modifications might be frozen. This is less common for SL adjustments far from price but can occur.
Debugging Your MQL5 Code
Print()andPrintFormat(): Liberally use these to output variable values, decision points, and error codes (GetLastError()).- MetaEditor Debugger: Step through your code, inspect variables, and understand the execution flow.
- Journal/Log Files: Check the MetaTrader Experts and Journal tabs for errors or custom messages.
- Strategy Tester Visual Mode: Observe how the TSL behaves on historical data bar by bar.
Optimizing Trailing Stop Loss Parameters
- Strategy Tester: Use MetaTrader 5’s optimizer to find optimal
TrailingStopPips,ATRMultiplier,TrailingStepPips, etc., for your specific strategy and instrument. - Walk-Forward Optimization: A more robust method than simple backtesting optimization to reduce curve-fitting.
- Consider Trade-offs: Tighter TSLs protect profits quickly but may exit prematurely. Wider TSLs allow trades more room to breathe but risk giving back more profit. The optimal balance is strategy-dependent.
- Robustness Checks: Test optimized parameters on different time periods (out-of-sample data) and slightly varied market conditions to ensure they are not over-optimized.
By understanding and correctly implementing trailing stop losses in MQL5, traders can significantly enhance their automated trading strategies, balancing profit maximization with prudent risk management.