Skip to main content

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

Popular posts from this blog

MetaTrader 4 Indicator to show Currency trend in Different Timeframes

Usage When trading, you probably have several windows open next to each other, different products, different time frames. Or, you have the same product but in different timeframes to see how it performs.
I wrote this indicator so I can see for a given product, how it behaves in the different timeframes available by MetaTradre 4. It has three different lines to express the following information: The trend for a given timeframe, blue arrow means bullish, red arrow means bearish.The pips change for a given timeframe, blue text means bullish, red text means bearish.The standard deviation for a given timeframe, red text means that the timeframe movement is higher thatn the volatility, otherwise black. Once attached, a panel will be drawn in the lower left corner as in the following screenshot for EURUSD:

You may see, that the daily trend for example is bullish. And that the hourly movement in pips is higher than the volatility, thus the STD value for hourly timeframe is painted in red.

To …

Mutual Exclusion (Mutex) Implementation in MQL4

Introduction In computer science, mutual exclusion is a property of concurrency control, which is instituted for the purpose of preventing race conditions; it is the requirement that one thread of execution never enters its critical section at the same time that another concurrent thread of execution enters its own critical section.

In Algotrading, you may encounter this situation while:
Running several expert advisers using a shared object which may worked on only once and not in parallel.Trading only in certain conditions, for example allowing only one expert adviser to open a position while the other ones are blocked. In my case, I am running several expert advisers on the same account trading different currencies. But, I wanted to have only one product open at a given moment while the others are blocked. So, if for example both EURUSD & EURJPY have the right conditions to open an order, only one order will be open. I wanted to avoid to be double exposed to products which are c…

How I make Money trading Forex

Did you know that on average 80% of retail traders lose money when trading Forex?

But, if you've been following my blog, you realized by now that I make my income from trading Forex, and to be specific from trading EURUSD only. This is the only product I trade and live from, which is quite counter intuitive, how is it possible to trade only one product with all it's ups and downs and live from it? In this post, I will explain exactly how I make a living from trading Forex and which traps to avoid.

Still, even that it worked for me and the setup is quite simple, there's no guarantee it will work for you the reader. We are all in a different situations in life, with more or less to risk, knowledge and so on. But still, I suggest to read this post to understand which traps are out there and which path you can follow to avoid losing your money.

The Traps



But first, let's start with the typical pitfalls out there, unfortunately, in some of them I fell myself and lost money:

Buyi…