Technical revision: 1 October 2026 · Educational material
A trailing stop changes the requested exit level after price moves in favor of a position. It changes the distribution of exits; it does not prove that a strategy has an edge. Compare it with a fixed-stop baseline using the same entries, costs and validation periods.
Manage only the positions you own
Iterating over all account positions while using the chart symbol’s prices can modify an unrelated trade. The example selects each ticket, then requires both the chart symbol and the configured magic number to match. A ticket-based modification targets the selected position explicitly.
On a netting account, trades on the same symbol contribute to one net position. A magic-number filter cannot fully separate shared ownership of that net exposure. Keep this demonstration on an isolated demo symbol rather than combining manual trades and several EAs there.
Define activation, distance and minimum improvement
The manager waits until the favorable movement reaches InpStartPoints. It then places a buy stop below bid or a sell stop above ask at the configured distance, widened only as necessary for reported stop and freeze constraints. The requested improvement must meet InpStepPoints, and the candidate must be beyond entry.
These are point distances. With spread, commission and swaps, a stop beyond the entry price still does not guarantee an overall profitable exit. A gap can execute beyond the requested level.
Use the downloadable manager
FtsTrailingDemo.mq5 is a separate EA which never opens or closes trades directly. Management defaults to off and is restricted to demo/tester execution. Put FtsRisk.mqh in the same folder for its tick-grid rounding helper.
void TrailPosition(const ulong ticket)
{
if(!PositionSelectByTicket(ticket)) return;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol ||
(ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagic) return;
long type=PositionGetInteger(POSITION_TYPE);
if(type!=POSITION_TYPE_BUY && type!=POSITION_TYPE_SELL) return;
double entry=PositionGetDouble(POSITION_PRICE_OPEN);
double existing=PositionGetDouble(POSITION_SL);
double target=PositionGetDouble(POSITION_TP);
MqlTick tick;
if(!SymbolInfoTick(_Symbol,tick)) return;
double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double tick_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(point<=0.0 || tick_size<=0.0 || tick.bid<=0.0 || tick.ask<=0.0) return;
double freeze=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)*point;
This excerpt shows the selection and quote checks from the complete file; the package contains the full activation, freeze-level and modification logic. Attach the entry EA and manager to separate charts because one chart hosts one EA.
Check constraints before modifying
- Read tick size separately from digits, and round the candidate in the protective direction.
- Check the reported stops level and freeze level in price units.
- Skip changes while the existing stop or target is inside the reported freeze zone.
- Preserve the existing take-profit value when changing the stop.
- Verify both the method result and
ResultRetcode; log rejected modifications.
The demonstration limits attempts to one pass per server second. A broker can still reject a request because the quote moved, the market is closed or server constraints differ. A production manager needs request throttling, transaction reconciliation and an explicit recovery policy.
Compare exits, not selected success stories
In the tester, record the initial stop, every proposed change, every accepted change and the final exit. Test widening spreads, price gaps, an unchanged candidate, positions with the wrong magic number, and several matching tickets on a hedging demo account.
Compare fixed and trailing exits over an untouched validation period. Include modification failures and costs in the record. Use the backtesting checklist and broker-error guide to make the comparison reproducible.
Reference: CTrade::PositionModify.