MQL5 Trailing Stop Code: Complete EA Function with CTrade & Step Logic
Inspect an educational CTrade trailing-stop manager with symbol/magic filtering, point-based distance and step, tick-grid rounding, stop/freeze checks and server-return validation. It opens no trades and defaults to disabled.
What this MQL5 guide covers
A trailing stop requests a more favorable stop price; it cannot guarantee a profitable fill or eliminate gap risk.
A trailing step filters small stop improvements; it does not impose a time-based request-rate limit or backoff after rejection.
Validate current quote, tick grid, stops and freeze distances; the server can still reject a request as market conditions change.
CTrade::PositionModify returns request-check status; inspect ResultRetcode and filter ownership explicitly.
Trailing Stop Mechanics: Buy vs Sell Positions
The sample uses Bid for buys and Ask for sells. Its effective distance is the larger of the input distance and broker stops level. Activation requires a move strictly beyond that distance.
Candidate buy stops round down to the tick grid; sell stops round up. The sample skips candidates not beyond entry and those within freeze constraints, preserves TP and never intentionally loosens an existing stop.
| Position Type | Trigger Condition | Proposed New Stop Loss | Modification Criterion |
|---|---|---|---|
| Buy (Long) | Bid - OpenPrice > effectiveDistance | FloorToTick(Bid - effectiveDistance) | Proposed SL > OpenPrice and (no SL or improvement > step) |
| Sell (Short) | OpenPrice - Ask > effectiveDistance | CeilToTick(Ask + effectiveDistance) | Proposed SL < OpenPrice and (no SL or improvement > step) |
Broker Rules: Stops Level and Freeze Level Checks
Before calling PositionModify, automated systems must respect two broker constraints: SYMBOL_TRADE_STOPS_LEVEL and SYMBOL_TRADE_FREEZE_LEVEL.
TRADE_RETCODE_INVALID_STOPS (10016) indicates invalid stops. Too-small distance is one cause; wrong-side prices, price increments or changing quotes can also invalidate a request. A local check is not a server acceptance guarantee.
TRADE_RETCODE_FROZEN is 10029; 10022 is invalid expiration. The example conservatively skips when the existing SL or TP is within the freeze distance and checks the candidate distance as well. Inspect actual broker responses.
- SYMBOL_TRADE_STOPS_LEVEL: Minimum points allowed between Bid/Ask and StopLoss.
- SYMBOL_TRADE_FREEZE_LEVEL: Price window where position stops cannot be changed.
- Decimal digits and SYMBOL_TRADE_TICK_SIZE are different constraints; decimal rounding alone does not establish a valid price increment.
Why TrailingStep Matters in Production EAs
Frequent eligible changes or repeated failures can generate many requests. This example has no persistent queue or retry backoff; a production design must explicitly choose request-rate controls and record server responses.
The step is an example in broker points, not a portable pip recommendation. Check the contract and compare logs before choosing a value.
Implementation details & mechanics
Core implementation pattern
The trailing stop logic is called inside OnTick() after regular position updates. Iterate through open positions using PositionsTotal() and PositionGetTicket(i). Verify PositionGetString(POSITION_SYMBOL) == _Symbol and PositionGetInteger(POSITION_MAGIC) == MagicNumber before evaluating whether current Bid/Ask qualifies for a stop modification.
Error handling & safety rules
Calculate the candidate new stop price. If the position is a Buy and candidate SL is greater than current SL + (TrailingStep * Point), or if the position is a Sell and candidate SL is less than current SL - (TrailingStep * Point), call trade.PositionModify(ticket, candidateSL, currentTP). Check trade.ResultRetcode() to confirm modification. On netting accounts, one symbol position can contain several strategies; magic filtering alone does not isolate their contributions.
MQL5 Code Implementation
//+------------------------------------------------------------------+
//| TrailingStopGuide.mq5 |
//| Copyright 2026, Pineify Guides Series |
//| https://pineify.app |
//+------------------------------------------------------------------+
#property copyright "Pineify Guides Series"
#property link "https://pineify.app/mql5/trailing-stop-code"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//--- Input parameters
input group "--- Trailing Stop Configuration ---"
input bool InpEnableTrailing = false; // Enable Trailing Stop
input int InpTrailingDistance = 300; // Broker points, not a universal pip conversion
input int InpTrailingStep = 50; // Minimum improvement in broker points
input ulong InpMagicNumber = 202602; // EA Magic Number
//--- Global trade handler
CTrade trade;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(InpTrailingDistance<=0 || InpTrailingStep<0) return INIT_PARAMETERS_INCORRECT;
trade.SetAsyncMode(false);
trade.SetExpertMagicNumber(InpMagicNumber);
Print("TrailingStop EA initialized on ", _Symbol,
" | Distance: ", InpTrailingDistance, " pts | Step: ", InpTrailingStep, " pts");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Function: Apply Trailing Stop to All Open Positions |
//+------------------------------------------------------------------+
void ApplyTrailingStop()
{
if(!InpEnableTrailing)
return;
double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double tickSize=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
MqlTick tick;
if(point<=0 || tickSize<=0 || !SymbolInfoTick(_Symbol,tick) || tick.bid<=0 || tick.ask<=0) return;
double bid=tick.bid,ask=tick.ask;
int stopsLevel=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
double freeze=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)*point;
double minDistance=MathMax(InpTrailingDistance,stopsLevel)*point;
double stepDistance=InpTrailingStep*point;
int totalPositions = PositionsTotal();
for(int i = totalPositions - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
// Filter by symbol and magic number
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
if(freeze>0 && ((currentSL>0 && MathAbs((posType==POSITION_TYPE_BUY?bid:ask)-currentSL)<=freeze) ||
(currentTP>0 && MathAbs((posType==POSITION_TYPE_BUY?bid:ask)-currentTP)<=freeze))) continue;
//--- BUY Position Trailing ---
if(posType == POSITION_TYPE_BUY)
{
// Check if price moved far enough in profit
if((bid - openPrice) > minDistance)
{
double candidateSL=NormalizeDouble(MathFloor((bid-minDistance)/tickSize)*tickSize,digits);
if(candidateSL<=openPrice || bid-candidateSL<=freeze || bid-candidateSL<stopsLevel*point) continue;
// Only modify if candidate SL improves current SL by at least TrailingStep
if(currentSL==0 || candidateSL > (currentSL + stepDistance))
{
if(trade.PositionModify(ticket, candidateSL, currentTP) && trade.ResultRetcode()==TRADE_RETCODE_DONE)
{
PrintFormat("Trailing Stop updated for BUY #%I64u: SL %.5f -> %.5f",
ticket, currentSL, candidateSL);
}
else
{
PrintFormat("Failed to update Trailing Stop #%I64u: %s",
ticket, trade.ResultRetcodeDescription());
}
}
}
}
//--- SELL Position Trailing ---
else if(posType == POSITION_TYPE_SELL)
{
// Check if price moved far enough in profit
if((openPrice - ask) > minDistance)
{
double candidateSL=NormalizeDouble(MathCeil((ask+minDistance)/tickSize)*tickSize,digits);
if(candidateSL>=openPrice || candidateSL-ask<=freeze || candidateSL-ask<stopsLevel*point) continue;
// Only modify if candidate SL improves current SL (lower is better for shorts)
if(currentSL == 0.0 || candidateSL < (currentSL - stepDistance))
{
if(trade.PositionModify(ticket, candidateSL, currentTP) && trade.ResultRetcode()==TRADE_RETCODE_DONE)
{
PrintFormat("Trailing Stop updated for SELL #%I64u: SL %.5f -> %.5f",
ticket, currentSL, candidateSL);
}
else
{
PrintFormat("Failed to update Trailing Stop #%I64u: %s",
ticket, trade.ResultRetcodeDescription());
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
ApplyTrailingStop();
}
Copy this code into MetaEditor, save it in your MQL5 folder (Experts, Scripts, or Include), and compile with F7.
Generate custom MQL5 code with Pineify AI
Describe the trade execution, indicator calculations, risk management, or data processing logic you need. Pineify generates editable MQL5 source code. Review its logic, request a syntax check, and compile and test the final file in MetaEditor.
Frequently Asked Questions
Related MQL5 pages
Technical references
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.