MQL5 Trailing Stop Code: Complete EA Function with CTrade & Step Logic
Production-ready MQL5 trailing stop code for MetaTrader 5 Expert Advisors. Includes point-based step logic, stop level distance checks, CTrade integration, and multi-position filtering.
What this MQL5 guide covers
A trailing stop moves the stop-loss price closer to market price as favorable price movement occurs, locking in accrued profits.
Implementing a Trailing Step prevents excessive server requests by only modifying the stop loss when price advances by a defined threshold.
Always verify SYMBOL_TRADE_STOPS_LEVEL to ensure the new stop loss distance satisfies the broker’s minimum required buffer.
CTrade::PositionModify(ticket, newSL, tp) executes the stop adjustment cleanly while isolating positions via MagicNumber.
Trailing Stop Mechanics: Buy vs Sell Positions
Trailing stops calculate new stop levels differently for Buy (long) and Sell (short) positions. For a long position, trailing triggers when the Bid price moves above the open price by at least TrailingDistance points.
For a short position, trailing triggers when the current Ask price falls below the open price by TrailingDistance points, and moves the stop loss down as market price descends.
| Position Type | Trigger Condition | Proposed New Stop Loss | Modification Criterion |
|---|---|---|---|
| Buy (Long) | Bid - OpenPrice >= TrailingDistance * Point | Bid - TrailingDistance * Point | Proposed SL > Current SL + TrailingStep * Point |
| Sell (Short) | OpenPrice - Ask >= TrailingDistance * Point | Ask + TrailingDistance * Point | Proposed SL < Current SL - TrailingStep * Point (or Current SL == 0) |
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.
Stops Level dictates the minimum distance (in points) that any Stop Loss or Take Profit must maintain from current market prices. Attempting to place a stop inside this zone triggers execution error 10016 (TRADE_RETCODE_INVALID_STOPS).
Freeze Level prevents modifying orders or positions when market price is currently within a critical threshold of existing stops. Modifying inside the freeze zone triggers error 10022 (TRADE_RETCODE_FROZEN).
- SYMBOL_TRADE_STOPS_LEVEL: Minimum points allowed between Bid/Ask and StopLoss.
- SYMBOL_TRADE_FREEZE_LEVEL: Price window where position stops cannot be changed.
- NormalizeDouble(price, digits): Required for every floating-point price calculation.
Why TrailingStep Matters in Production EAs
Without a Trailing Step, a fast-moving market would trigger an order modification request on every single tick. Most MetaTrader 5 brokers impose frequency limits on trade requests and may temporarily disconnect accounts sending excessive modifications.
A TrailingStep of 10 to 50 points ensures that the stop loss only updates when price advances meaningfully beyond the previous level.
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.
MQL5 Code Implementation Code
//+------------------------------------------------------------------+
//| 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 = true; // Enable Trailing Stop
input int InpTrailingDistance = 300; // Trailing distance in points (e.g. 30 pips)
input int InpTrailingStep = 50; // Trailing step in points (e.g. 5 pips)
input ulong InpMagicNumber = 202602; // EA Magic Number
//--- Global trade handler
CTrade trade;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
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);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int stopsLevel= (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
// Effective minimum distance required by broker
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);
//--- BUY Position Trailing ---
if(posType == POSITION_TYPE_BUY)
{
// Check if price moved far enough in profit
if((bid - openPrice) > minDistance)
{
double candidateSL = NormalizeDouble(bid - minDistance, digits);
// Only modify if candidate SL improves current SL by at least TrailingStep
if(candidateSL > (currentSL + stepDistance))
{
if(trade.PositionModify(ticket, candidateSL, currentTP))
{
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(ask + minDistance, digits);
// 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))
{
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 produces clean, typed MQL5 source code ready to compile in MetaEditor.
Frequently Asked Questions
Related MQL5 pages
Technical references
Risk and testing note
Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.