Critical Issue with Stepped Trailing Stop: Premature and Incorrect Stop Loss Update

 

Hello everyone,

I'm experiencing a critical issue with a custom stepped trailing stop that I've implemented in my MQL5 Expert Advisor (EA).

The trailing stop logic should be as follows:

  • Stepped Logic: The trailing stop moves the stop loss in fixed increments of 10 points.
  • Favorable Movement Only: The stop loss updates only when the price moves 10 points in favor of the position.
  • Restriction: The stop loss can only tighten (move closer to the price), never widen.
  • Crossing the Entry Price: The stop loss can cross the entry price to ensure a minimum profit.
  • No Reversal: the trailing stop can never go back until the take profit or the stop loss is reached.
  • Incremental Update: the stop loss must be updated incrementally, starting from the initial stop loss and moving 10 points at a time.

However, during backtests, I've observed an anomalous and unacceptable behavior:

  • The trailing stop activates and moves the stop loss too quickly, even with price movements smaller than the intended 10 points.
  • In practice, the stop loss moves below or above the entry price as soon as the price moves slightly in favor of the position, completely ignoring the initial stop loss level and the 10-point threshold.
  • This behavior renders the trailing stop unusable, as it exposes capital to excessive and unpredictable risks.

Example:

  • I open a BUY position at 1.10000 with an initial stop loss at 1.09900 (100 points away).
  • I expect the stop loss to remain at 1.09900 until the price reaches 1.10010.
  • Instead, as soon as the price rises to 1.10002 (only 2 points of profit), the stop loss immediately moves to 1.10000 (the entry price), completely ignoring the initial stop loss level and the 10-point threshold.

I've carefully examined the code of the UpdateTrailingStop() function, but I can't pinpoint the cause of the problem. I suspect there's an error in the logic for calculating the new stop loss or in managing the update conditions.

I'm attaching the code of the UpdateTrailingStop() function for your analysis:

// Variables for Step Trailing Stop
double trailingStopLevel = 10 * Point(); // Step size: 10 points
double currentStopLoss = 0; // Current stop loss level
double openPrice = 0; // Position opening price

// Function to calculate dynamic lot size
double CalculateLotSize(double high, double low)
{
    double capital = AccountInfoDouble(ACCOUNT_BALANCE);
    double risk = capital * 0.0025; // 0.25% of capital
    double differencePoints = (high - low) / SymbolInfoDouble(Symbol(), SYMBOL_POINT);
    double pointValue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE);

    if (differencePoints <= 0 || pointValue <= 0) return 0; // Avoid division by zero

    double lot = risk / (differencePoints * pointValue);
    return NormalizeDouble(lot, 2); // Keep only two decimal places
}

// Function to close all positions
void CloseAllPositions()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (ticket > 0)
        {
            trade.PositionClose(ticket);
        }
    }
    posizioneAperta = false; // positionOpened = false;
}

// Function for price monitoring and Step Trailing Stop
void MonitorPriceAndStepTrailingStop()
{
    Print("MonitorPriceAndStepTrailingStop called"); // Debug: confirm that the function is called

    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (ticket > 0)
        {
            // Verify that the position belongs to the correct symbol
            if (PositionGetString(POSITION_SYMBOL) == Symbol())
            {
                if (PositionSelectByTicket(ticket))
                {
                    double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
                    double priceDifference = (currentPrice - openPrice) / Point(); // Difference in points

                    Print("Position found: Ticket = ", ticket, ", Type = ", PositionGetInteger(POSITION_TYPE), ", Current SL = ", currentStopLoss, ", Current price = ", currentPrice, ", Difference = ", priceDifference); // Debug: position information

                    if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                    {
                        // Price monitoring for a BUY position
                        if (currentPrice - openPrice >= trailingStopLevel) // If the price has moved 10 points in favor relative to the opening price
                        {
                            double newStopLoss = openPrice + (MathFloor((currentPrice - openPrice) / trailingStopLevel) * trailingStopLevel);
                            if (newStopLoss > currentStopLoss) // Make sure the stop loss tightens
                            {
                                trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));
                                currentStopLoss = newStopLoss;
                                Print("Step Trailing Stop updated for BUY: new SL = ", DoubleToString(newStopLoss, 5));
                            }
                        }
                    }
                    else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
                    {
                        // Price monitoring for a SELL position
                        if (openPrice - currentPrice >= trailingStopLevel) // If the price has moved 10 points in favor relative to the opening price
                        {
                            double newStopLoss = openPrice - (MathFloor((openPrice - currentPrice) / trailingStopLevel) * trailingStopLevel);
                            if (newStopLoss < currentStopLoss) // Make sure the stop loss tightens
                            {
                                trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));
                                currentStopLoss = newStopLoss;
                                Print("Step Trailing Stop updated for SELL: new SL = ", DoubleToString(newStopLoss, 5));
                            }
                        }
                    }
                }
                else
                {
                    Print("Error: unable to select position with ticket ", ticket); // Debug: error in position selection
                }
            }
            else
            {
                Print("Error: position does not belong to the correct symbol"); // Debug: position on a different symbol
            }
        }
        else
        {
            Print("Error: invalid ticket for position ", i); // Debug: invalid ticket
        }
    }
}

Thank you in advance for your help.

 
Nicola Mungiguerra:
I have moved your topic to the "Expert Advisors and Automated Trading" section. Please keep this in mind for future reference.
 
Nicola Mungiguerra:

Hello everyone,

I'm experiencing a critical issue with a custom stepped trailing stop that I've implemented in my MQL5 Expert Advisor (EA).

The trailing stop logic should be as follows:

  • Stepped Logic: The trailing stop moves the stop loss in fixed increments of 10 points.
  • Favorable Movement Only: The stop loss updates only when the price moves 10 points in favor of the position.
  • Restriction: The stop loss can only tighten (move closer to the price), never widen.
  • Crossing the Entry Price: The stop loss can cross the entry price to ensure a minimum profit.
  • No Reversal: the trailing stop can never go back until the take profit or the stop loss is reached.
  • Incremental Update: the stop loss must be updated incrementally, starting from the initial stop loss and moving 10 points at a time.

However, during backtests, I've observed an anomalous and unacceptable behavior:

  • The trailing stop activates and moves the stop loss too quickly, even with price movements smaller than the intended 10 points.
  • In practice, the stop loss moves below or above the entry price as soon as the price moves slightly in favor of the position, completely ignoring the initial stop loss level and the 10-point threshold.
  • This behavior renders the trailing stop unusable, as it exposes capital to excessive and unpredictable risks.

Example:

  • I open a BUY position at 1.10000 with an initial stop loss at 1.09900 (100 points away).
  • I expect the stop loss to remain at 1.09900 until the price reaches 1.10010.
  • Instead, as soon as the price rises to 1.10002 (only 2 points of profit), the stop loss immediately moves to 1.10000 (the entry price), completely ignoring the initial stop loss level and the 10-point threshold.

I've carefully examined the code of the UpdateTrailingStop() function, but I can't pinpoint the cause of the problem. I suspect there's an error in the logic for calculating the new stop loss or in managing the update conditions.

I'm attaching the code of the UpdateTrailingStop() function for your analysis:

Thank you in advance for your help.

The "jump" floor method is really neat but in this case you just need to maintain the original distance of the stop loss at all times  , and , make sure any new sl is bigger by trailing step than the previous one.

If i understood correctly that is.

#include <Trade\Trade.mqh>
CTrade trade;
bool placed=false;
double initialStopSize=0.0;
double trailingStopLevel=0.0;
int OnInit()
  {
  placed=false;
  return(INIT_SUCCEEDED);
  }
void OnTick()
  {
  if(!placed){
    placed=true;
    MqlTick T;
    if(SymbolInfoTick(_Symbol,T)){
      trailingStopLevel=10.0*_Point;
      initialStopSize=100.0*_Point;
      double SL=NormalizeDouble(T.bid-initialStopSize,_Digits);
      trade.Buy(0.01,_Symbol,T.ask,SL,0.0);
      
      }
    }else{
    MonitorPriceAndStepTrailingStop();
    }
  }

// Function for price monitoring and Step Trailing Stop
void MonitorPriceAndStepTrailingStop()
{
    //Print("MonitorPriceAndStepTrailingStop called"); // Debug: confirm that the function is called

    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (ticket > 0)
        {
            // Verify that the position belongs to the correct symbol
            if (PositionGetString(POSITION_SYMBOL) == Symbol())
            {
                if (PositionSelectByTicket(ticket))
                {
                    double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
                    double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
                    double existingSL = PositionGetDouble(POSITION_SL);
                    //buy
                      if(PositionGetInteger(POSITION_TYPE)==ORDER_TYPE_BUY){
                      double distance=currentPrice-existingSL;
                          if(distance>=initialStopSize){
                            //project new stop loss by taking into account the original SL size
                              double new_sl=currentPrice-initialStopSize;
                              //if it is higher than before set it 
                                if(new_sl>=existingSL+trailingStopLevel){
                                  trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
                                  }
                            }
                        }
                    //sell
                      else if(PositionGetInteger(POSITION_TYPE)==ORDER_TYPE_SELL){
                      double distance=existingSL-currentPrice;
                          if(distance>=initialStopSize){
                            //project new stop loss by taking into account the original SL size
                              double new_sl=currentPrice+initialStopSize;
                              //if it is higher than before set it 
                                if(new_sl<=existingSL-trailingStopLevel){
                                  trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP));
                                  }
                            }
                        }
                    
                }
                else
                {
                    Print("Error: unable to select position with ticket ", ticket); // Debug: error in position selection
                }
            }
            else
            {
                Print("Error: position does not belong to the correct symbol"); // Debug: position on a different symbol
            }
        }
        else
        {
            Print("Error: invalid ticket for position ", i); // Debug: invalid ticket
        }
    }
}
 
Miguel Angel Vico Alba # : Ho spostato il tuo argomento nella sezione "Expert Advisors e Trading Automatizzato ". Tienilo a mente per riferimento futuro.
Thanks, sorry but I didn't see that there was an EA section.
 
Lorentzos Roussos # :

Il metodo del floor "jump" è davvero interessante, ma in questo caso devi solo mantenere sempre la distanza originale dello stop loss e assicurarti che ogni nuovo sl sia più grande del precedente per un trailing step.

Se ho capito bene, cioè.



Hi, thanks for your suggestion, I've now resolved it this way. The problem has been solved, although the step trailing stop is a bit slow to reposition itself.
#include <Trade/Trade.mqh>
CTrade trade;

// Strategy settings
double StepTrailingStop = 10; // Step of 10 basis points

double PipValue() {
    return SymbolInfoDouble(_Symbol, SYMBOL_POINT);
}

void CheckTrailingStop() {
    if (PositionSelect(_Symbol)) {
        double price = PositionGetDouble(POSITION_PRICE_OPEN);
        double sl = PositionGetDouble(POSITION_SL);
        double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
        int type = PositionGetInteger(POSITION_TYPE);

        if (type == POSITION_TYPE_BUY) {
            // Calculate the distance from the opening price
            double distanceFromOpen = currentPrice - price;
            // If the price has risen by 10 pips from the opening price
            if (distanceFromOpen >= StepTrailingStop * PipValue()) {
                // Calculate the new SL as the previous SL + 10 pips
                double newSL = sl + StepTrailingStop * PipValue();
                // Ensure the new SL is not higher than the current price
                if (newSL < currentPrice) {
                    if (!trade.PositionModify(PositionGetInteger(POSITION_TICKET), newSL, PositionGetDouble(POSITION_TP))) {
                        Print("[ERROR] Trailing Stop update failed (BUY): ", GetLastError());
                    } else {
                        Print("[INFO] Trailing Stop updated (BUY): New SL=", newSL);
                    }
                }
            }
        } else if (type == POSITION_TYPE_SELL) {
            // Calculate the distance from the opening price
            double distanceFromOpen = price - currentPrice;
            // If the price has fallen by 10 pips from the opening price
            if (distanceFromOpen >= StepTrailingStop * PipValue()) {
                // Calculate the new SL as the previous SL - 10 pips
                double newSL = sl - StepTrailingStop * PipValue();
                // Ensure the new SL is not lower than the current price
                if (newSL > currentPrice) {
                    if (!trade.PositionModify(PositionGetInteger(POSITION_TICKET), newSL, PositionGetDouble(POSITION_TP))) {
                        Print("[ERROR] Trailing Stop update failed (SELL): ", GetLastError());
                    } else {
                        Print("[INFO] Trailing Stop updated (SELL): New SL=", newSL);
                    }
                }
            }
        }
    }
}

void OnTick() {
    // ... (other parts of your OnTick function) ...

    // Check and update the trailing stop
    CheckTrailingStop();
}
 
Nicola Mungiguerra #:

the translator messes up the code