Implementing Trailing Stop Loss in MQL4

Cut your losses early, and let your winners runs.
Trading circles

Introduction

This is the most common quote you'll probably hear in trading circles. Which basically means, that as soon as you open a position, you need to monitor it and make a decision against it or for it. When you move into the algotrading regime, I would modify this quote into the following one:
Cut your losses early, and let your winners run... to your trailing stop loss.
Roy Meshulam
That's because you don't want to spend your time in front your PC monitoring the order. The contrary, you want to have a fully automated expert adviser that does it for you, opening and closing order according to the logic you built.

Trailing stop loss a strong utility to have. It is basically the logic to update the stop loss value and move it together with the price. So, if the price is in your favour, you move the stop loss, thus, capturing more profit. If it is against, you keep the stop loss with the same value. Hence, it is called trailing stop loss.


Stop Loss / Take Profit Function





The first thing we must have is a safe function to update an order with a stop loss / take profit value. We can't rely on the OrderModify one, as there is no proper error handling, value checking etc.




Function Signature





So, we will use our own function, which takes the following parameters:




bool AddStopProfit(int argOrder,double argStopLoss,double argTakeProfit,bool argForceCorrectValue=false) export




  • The order we want to modify
  • The stop loss value
  • The take profit value
  • A boolean flag to force a new value or not




The return value will be a boolean which indicates whether the value modifications succeeded or not.




Verification Step





Next, we need to check if the order is still open. If yes, whether the new stop loss / take profit values are different than the existing ones. If the order is closed already, or the values are the same, then, there is nothing to change so we will return true:




if(OrderCloseTime()!=0 || 
     (NormalizeDouble(argStopLoss,(int)MarketInfo(OrderSymbol(),MODE_DIGITS))==OrderStopLoss() && 
     NormalizeDouble(argTakeProfit,(int)MarketInfo(OrderSymbol(),MODE_DIGITS))==OrderTakeProfit()))
     return true;




Modification Step





Next, we will check what are the legal values for stop loss / take profit for the order. We will use as an example a long order. But the same logic will be applicable to a short one.




Long Order Stop Loss Value Verification





Normally, the broker has a maximum value for the stop loss. Which means, that if you try setting up one above this point, the OrderModify function will fail. To check what is this value we use the following code:




StopLoss=MarketInfo(OrderSymbol(),MODE_BID)-MarketInfo(OrderSymbol(),MODE_STOPLEVEL)*MarketInfo(OrderSymbol(),MODE_POINT);
        if(argStopLoss!=0 && argStopLoss>StopLoss)
          {
           if(argForceCorrectValue==false)
             {
              SendNotification(StringConcatenate("OP_BUY illegal SL for #",argOrder,", New / Allowed = ",argStopLoss,"/",StopLoss));
              return false;
             }
           else
              argStopLoss=StopLoss;
          }




The first line check what is the maximum stop loss value we can set by subtracting the stop level value from the current bid price. The MODE_STOPLEVEL according to https://docs.mql4.com/constants/environment_state/marketinfoconstants: Is the stop level in points. A zero value of MODE_STOPLEVEL means either absence of any restrictions on the minimal distance for Stop Loss/Take Profit or the fact that a trade server utilizes some external mechanisms for dynamic level control, which cannot be translated in the client terminal. In the second case, GetLastError() can return error 130, because MODE_STOPLEVEL is actually "floating" here.




If the argForceCorrectValue is set to false and the stop level value is illegal. The function will terminate and return false value, otherwise, we will use the maximum calculated stop level value instead of the function argument.




The same logic applies for the take profit, there we need to check the that take profit value is not smaller than the minimum one allowed by the broker.




Calling OrderModify





Finally, we will call MQL4 OrderModify function to set the stop loss / take profit value, either the calculated one or the one we got as an argument:




if(OrderModify(argOrder,OrderOpenPrice(),NormalizeDouble(argStopLoss,(int)MarketInfo(OrderSymbol(),MODE_DIGITS)),NormalizeDouble(argTakeProfit,(int)MarketInfo(OrderSymbol(),MODE_DIGITS)),0)==false)
       {
        SendNotification(StringConcatenate("Add Stop/Profit failed updating order #",argOrder,", SL (N/E) = ",argStopLoss,"/",OrderStopLoss(),", TP (N/E) = ",argTakeProfit,"/",OrderTakeProfit()),true,GetLastError());
        return false;
       }




With this step completed, we updated the order values successfully. A typical call to this function will be something like:




AddStopProfit(OrderTicket(), 1.23456,1.43589,true);




Meaning, modify the current ticket with a stop loss equal to 1.23456, take profit equals to 1.43589 and force valid values for them by setting the force flag to true.




Full Function Code





Putting it all together we have the following code:




    bool AddStopProfit(int argOrder,double argStopLoss,double argTakeProfit,bool argForceCorrectValue=false) export
  {
   if(OrderSelect(argOrder,SELECT_BY_TICKET))
     {
      if(OrderCloseTime()!=0 || 
         (NormalizeDouble(argStopLoss,(int)MarketInfo(OrderSymbol(),MODE_DIGITS))==OrderStopLoss() && 
         NormalizeDouble(argTakeProfit,(int)MarketInfo(OrderSymbol(),MODE_DIGITS))==OrderTakeProfit()))
         return true;
      else if(OrderCloseTime()==0)
        {
         double StopLoss,TakeProfit;
         if(OrderType()==OP_BUY)
           {
            StopLoss=MarketInfo(OrderSymbol(),MODE_BID)-MarketInfo(OrderSymbol(),MODE_STOPLEVEL)*MarketInfo(OrderSymbol(),MODE_POINT);
            if(argStopLoss!=0 && argStopLoss>StopLoss)
              {
               if(argForceCorrectValue==false)
                 {
                  Log(StringConcatenate("OP_BUY illegal SL for #",argOrder,", New / Allowed = ",argStopLoss,"/",StopLoss));
                  return false;
                 }
               else
                  argStopLoss=StopLoss;
              }

            TakeProfit=MarketInfo(OrderSymbol(),MODE_ASK)+MarketInfo(OrderSymbol(),MODE_STOPLEVEL)*MarketInfo(OrderSymbol(),MODE_POINT);
            if(argTakeProfit!=0 && argTakeProfit<TakeProfit)
              {
               if(argForceCorrectValue==false)
                 {
                  Log(StringConcatenate("OP_BUY illegal TP for order #",argOrder,", New / Allowed = ",argTakeProfit,"/",TakeProfit));
                  return false;
                 }
               else
                  argTakeProfit=TakeProfit;
              }
           }
         else if(OrderType()==OP_SELL)
           {
            StopLoss=MarketInfo(OrderSymbol(),MODE_ASK)+MarketInfo(OrderSymbol(),MODE_STOPLEVEL)*MarketInfo(OrderSymbol(),MODE_POINT);
            if(argStopLoss!=0 && argStopLoss<StopLoss)
              {
               if(argForceCorrectValue==false)
                 {
                  Log(StringConcatenate("OP_SELL illegal SL value for #",argOrder,", New / Allowed = ",argStopLoss,"/",StopLoss));
                  return false;
                 }
               else
                  argStopLoss=StopLoss;
              }

            TakeProfit=MarketInfo(OrderSymbol(),MODE_BID)-MarketInfo(OrderSymbol(),MODE_STOPLEVEL)*MarketInfo(OrderSymbol(),MODE_POINT);
            if(argTakeProfit!=0 && argTakeProfit>TakeProfit)
              {
               if(argForceCorrectValue==false)
                 {
                  Log(StringConcatenate("OP_SELL illegal TP for order #",argOrder,", New / Allowed = ",argTakeProfit,"/",TakeProfit));
                  return false;
                 }
               else
                  argTakeProfit=TakeProfit;
              }
           }
         if(OrderModify(argOrder,OrderOpenPrice(),NormalizeDouble(argStopLoss,(int)MarketInfo(OrderSymbol(),MODE_DIGITS)),NormalizeDouble(argTakeProfit,(int)MarketInfo(OrderSymbol(),MODE_DIGITS)),0)==false)
           {
            Log(StringConcatenate("Add Stop/Profit failed updating order #",argOrder,", SL (N/E) = ",argStopLoss,"/",OrderStopLoss(),", TP (N/E) = ",argTakeProfit,"/",OrderTakeProfit()),true,GetLastError());
            return false;
           }
         else
            return true;
        }
     }
   else
     {
      Log(StringConcatenate("Add Stop/Profit failed selecting order #",argOrder),true,GetLastError());
      return false;
     }
   return true;
  }




Trailing Stop Loss Implementation





Now that the we have a correct function to update an order with stop loss / take profit values. Implementing a trailing stop loss will use it whenever the ticket progress in the right direction:




double GAP=0.00100;

int Ticket=OrderTicket();
if((MarketInfo(Symbol(),MODE_BID)-OrderStopLoss())>GAP)
    AddStopProfit(Ticket,MarketInfo(Symbol(),MODE_BID)-GAP,0,true);




In this example, I chose a long order and checked whether the current price minus the order stop loss price is bigger than the predefined GAP value of 100 pips. If it is the case, I subtract the GAP from the current price. Thus, I ensure the gap is kept constant between the current price to the stop loss of 100 pips.




It is straightforward to use and I tried to keep it as simple as possible. Another possibility is to set the stop loss based on an indicator value, for example using the Trailing ATR indicator:









In this example, you may adjust the code to have the trailing stop equal the value of the Trailing ATR value. There's a really nice value capturing in this image if you calculate properly...




Summary





In this post, I introduce the code I use for algorithmic trailing stop value. You may use this code as is in your programs and update the stop loss every X minutes / every predefined event etc.




As usual, feel free to contact me for any query / topic. Join the telegram chat for free signals and trading ideas t.me/ForexAlgotrading.

Comments