MQL5 Trailing Stop EA: Update Stops Without Loosening Risk
Quick Answer
An MQL5 trailing stop recalculates a protective stop as price moves in a favorable direction and modifies the position only when the new stop improves protection. For a buy, a typical candidate stop is below the current Bid; for a sell, it is above the current Ask. Check the symbol's tick size, stop and freeze levels, position ownership, and the trade server's return code before assuming the change succeeded.
Key Facts
| Check | Buy position | Sell position |
|---|---|---|
| Price used for trailing reference | Bid | Ask |
| Candidate stop | Below current Bid | Above current Ask |
| Safe direction | Raise the stop, never lower it | Lower the stop, never raise it |
| Common broker constraint | Minimum distance and freeze level | Minimum distance and freeze level |
| Result verification | Inspect CTrade result retcode | Inspect CTrade result retcode |
A trailing stop is not a guarantee of a particular exit price. It is a rule for requesting stop-loss changes. A gap, fast move, rejected modification, or unavailable market can affect the eventual fill. Define the trailing rule in points, price, volatility units, or another explicit measure; do not mix those units.
A Simple Trailing-Stop Pattern
This example shows the central rule for a single netting-style position on the chart symbol. It raises a buy stop only when it improves the existing stop. It is illustrative: production code should handle position ownership, ticket selection on hedging accounts, price normalization to tick size, throttling, and server responses.
#include <Trade/Trade.mqh>
CTrade trade;
input int TrailPoints = 300;
void TrailChartPosition()
{
if (TrailPoints <= 0 || !PositionSelect(_Symbol))
return;
long type = PositionGetInteger(POSITION_TYPE);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
MqlTick tick;
if (point <= 0.0 || !SymbolInfoTick(_Symbol, tick))
return;
double distance = TrailPoints * point;
double candidateSL = 0.0;
if (type == POSITION_TYPE_BUY)
{
candidateSL = NormalizeDouble(tick.bid - distance, digits);
if (currentSL != 0.0 && candidateSL <= currentSL)
return;
}
else if (type == POSITION_TYPE_SELL)
{
candidateSL = NormalizeDouble(tick.ask + distance, digits);
if (currentSL != 0.0 && candidateSL >= currentSL)
return;
}
else
return;
bool modified = trade.PositionModify(_Symbol, candidateSL, currentTP);
if (!modified || trade.ResultRetcode() != TRADE_RETCODE_DONE)
Print("PositionModify request failed: ",
trade.ResultRetcodeDescription());
}
Call a trailing function from OnTick() only after deciding which positions the EA is allowed to manage. On a hedging account, multiple positions may exist for one symbol; select and modify the intended position by ticket rather than relying on a symbol-only operation. In netting mode, a symbol position can aggregate deals, so magic-number ownership is not a reliable way to isolate separate strategies sharing that symbol. true from a CTrade method is not by itself a server-confirmed modification; check its result retcode and re-read the position if exact state matters.
Broker Rules and Implementation Details
SYMBOL_TRADE_STOPS_LEVEL describes a minimum stop distance in points for the symbol. SYMBOL_TRADE_FREEZE_LEVEL describes a zone where certain operations may be restricted. These values can change by broker and symbol. Validate candidate prices against current market prices and the broker's requirements; a syntactically valid request can still be rejected.
Price digits are not always the same thing as the tradable tick increment. Where necessary, align prices to SYMBOL_TRADE_TICK_SIZE, not merely the number of decimal digits. Also consider checking only when the proposed change is at least one tick better, so the EA does not send near-identical modifications on every tick.
For example, a buy trailing rule might activate only after price has moved a defined distance in profit. Before activation, retain the initial stop. After activation, compute the candidate from the current Bid and configured trail distance; send a modification only if it is both valid for the broker and more protective than the current stop. A sell rule mirrors this using the Ask. If the EA is restarted, it should read the stop already attached to the position rather than assuming a fresh trade with no stop.
Avoid silently treating a failed modification as success. Record the retcode and description, refresh the current position and quote, and retry only if the error is transient and the stop still improves protection. A stop may have been hit or the position may have changed between reading and modifying it.
Other decisions to specify:
- When trailing starts: immediately, after a profit threshold, or after a break-even transition.
- Whether the initial stop is placed with the entry or later.
- Whether trailing distance is fixed or derived from volatility, such as ATR.
- Whether a rejected modification is retried, and under what conditions.
- Whether stops are managed per ticket, per symbol, or across the whole account.
Review your EA's risk-based lot sizing alongside stop logic. Volume determines planned exposure; the stop policy determines where the EA requests an exit.
Test the Stop, Not Just the Entry
In MT5 Strategy Tester, use a modelling mode and spread assumptions suitable for the strategy. Inspect a sample of stop modifications in the log, test both long and short cases, and include volatile periods. Compare requested stop prices to actual fills. A backtest cannot guarantee the same execution in a live market.
Frequently Asked Questions
Convert, Review, and Forward-Test
If you are translating a Pine exit rule, make its activation condition and bar timing explicit before implementing it in MQL5. CodeFlowOS can help produce compiler-verified MQL5 output as a starting point; review the stop logic and test it in Strategy Tester and on demo before live use. See CodeFlowOS pricing for product details.