Русский
preview
How to add Trailing Stop using Parabolic SAR

How to add Trailing Stop using Parabolic SAR

MetaTrader 5Examples | 10 September 2024, 12:55
1 012 7
Artyom Trishkin
Artyom Trishkin

Contents



Introduction

Trailing stop is well known to most traders. This function is built into MetaTrader 5 and automatically adjusts the StopLoss level, maintaining it at a certain distance from the current price:

Enabling Trailing Stop in MetaTrader 5


Trailing stop is an automatic shift of StopLoss position behind the price, allowing to constantly keep the protective stop at some distance from the price. This approach allows the trader to protect part of the accumulated profit without exiting the position prematurely. Each time the market price moves away from the position opening price, the trailing stop automatically tightens the StopLoss, maintaining the specified distance between it and the current price. However, if the price approaches the opening price, StopLoss remains at the same level. This provides protection against losses due to possible market fluctuations.

However, if you need a more specialized version of the trailing stop, you can always develop a function in MQL5 to extend the capabilities of the standard tool.

There is a program function, to which the required price is passed to set the StopLoss level. The program checks some prohibiting factors, such as the StopLevel level - the distance, closer to which stops cannot be placed, or the FreezeLevel level - the freezing distance, within which a position or pending order cannot be modified. In other words, if the price has approached the stop level of the position at a distance closer than the FreezeLevel level, then the stop order is expected to be triggered, and modification is prohibited. Trailing stops also have some individual parameter settings that are also checked before moving the stop loss level to the specified price, for example, the symbol and magic of the position. All these criteria are checked immediately before moving the StopLoss position to the specified level.

Different types of trailing have different algorithms for calculating the StopLoss price of a position, which are passed to the trailing function and which are then used by the trailing stop function.

Parabolic SAR indicator perfectly serves as a "pointer" of the levels required by StopLoss.



Parabolic SAR (Stop and Reverse) indicator is a popular tool in technical analysis used to determine the moments of the current trend possible end and reversal. This indicator was developed by Welles Wilder and is often used for auto trailing stop losses. Here are the main reasons why the Parabolic SAR indicator is attractive for trailing a protective stop:

  1. Ease of interpretation: Parabolic SAR is easy to interpret as it is represented on the chart as dots placed above or below the price. When the dots are below the prices, it is a buy signal, when the dots are above the price, it is a sell signal.

  2. Automatic price following : The main advantage of the Parabolic SAR is its ability to automatically adapt to price changes and move over time. This makes it a perfect tool for setting trailing stops, as it provides profit protection by pulling the stop loss closer to the current price as the trend moves.

  3. Profit protection: As the price moves towards the profit of the open position, Parabolic SAR pulls up the stop loss level, which helps to protect part of the accumulated profit from a possible trend reversal.

  4. Exit signals: In addition to the trailing stop function, Parabolic SAR can also serve as a signal to close a position when the indicator dots cross the price. This can prevent further losses when the trend changes quickly.

  5. Easy setup: Parabolic SAR parameters (step and maximum) can be easily adjusted to adapt to specific market volatility or trading strategy. This makes it a versatile tool for a variety of trading conditions.

  6. Suitable for all timeframes: The indicator can be effectively used in various timeframes, which makes it suitable for both long-term investors and traders working on short intervals.

Using the Parabolic SAR for a trailing stop is especially useful in trending markets, where this tool helps to maximize profits while allowing you to keep a position open as long as the trend continues. However, keep in mind that during flat periods or low volatility, using Parabolic SAR may lead to premature closing of positions due to frequent changes in the position of the indicator dots.


Parabolic SAR trailing

Let's look at the structural diagram of any trailing.

Typically, the trailing stop code consists of several self-sufficient blocks that can be separated from the general structure and designed as functions:

  1. block for calculating the required StopLoss level; the obtained value is passed to the Trailing StopLoss block.
  2. Trailing StopLoss block includes
    1. filter block
    2. the block for setting StopLoss to the value obtained from the StopLoss level calculation block includes
      1. block of filters for compliance with server conditions regarding symbol levels, as well as conditions for shifting StopLoss.
      2. StopLoss modification block.

Block for calculating the required StopLoss level — here it is Parabolic SAR. Its value, usually from bar 1, is sent to the Trailing StopLoss block on each tick, where the properties of each selected position pass through the filter block in a loop through the list of open positions - usually by symbol and magic number. Next, if the filters by symbol/magic are passed, then the required StopLoss level is subject to additional filtering for compliance with the conditions for the server StopLevel, the trailing step, the value of the required StopLoss relative to its previous position and the criterion for starting the trailing by the position profit in points. If these filters are also passed, the StopLoss of the position is modified to be set to a new level.

Thus, we already have a block for calculating the stop loss level - this is the Parabolic SAR indicator. This means that it is only necessary to make a block of shifting StopLoss levels of positions selected by the current symbol and EA ID (Magic Number). If the magic value is set to -1, then any position opened on the chart symbol will be trailed. If a magic number is specified, only positions with the corresponding magic number will be trailed. The trailing function will be launched only when a new bar or a new position is opened. Let's provide an example as an EA.

In the EA, we will create the Parabolic SAR indicator with the parameters specified in the EA settings. The indicator values taken from a given bar (by default, from the first one) will be passed to the trailing stop function, which in turn will perform all the necessary calculations to shift the StopLoss levels of positions. It will be necessary to take into account the StopLevel level for the symbol, closer to which stops cannot be placed. The current StopLoss level will also be checked and if it is the same, or higher (for buying), or lower (for selling) than the level passed to the function, then the stop does not need to be shifted.

All these checks will be handled by a special function for checking the criteria for modifying the stop loss position. After all necessary checks have been completed, the stop position will be moved to a new level using the stop modification function.

In the \MQL5\Experts\ terminal folder, create a new EA file called TrailingBySAR_01.mq5.

In the second step of the wizard for creating a new EA file, check the OnTradeTransaction() handler in the new window:


The OnTradeTransaction() handler is required to launch the trailing at the moment of opening a new position.

The handleris called in case of the TradeTransaction event, which also features the opening of a new position.

Find out more about trading transactions and events in the article "Creating a Trading Robot for Moscow Exchange. Where to Start?".

We will add the following inputs and global variables to the created EA file:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_01.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum

//--- global variables
int      ExtHandleSAR =INVALID_HANDLE// Parabolic SAR handle
double   ExtStepSAR   =0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;                 // Parabolic SAR maximum

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 

  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {

  }


In the EA's OnInit() handler, set the correct values for the parameters entered in the indicator settings, create the indicator and get its handle:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);

//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }
//--- successful
   return(INIT_SUCCEEDED);
  }

If the indicator is successfully created, its handle will be created to be used to continue receiving values from Parabolic SAR. If an error occurs while creating an indicator, an error message with the data of the indicator being created is displayed in the journal. To describe the timeframe the indicator is based on, we use the function that returns a timeframe text description:

//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }

We get the part of the enumeration text from the timeframe constant obtaining a string containing only the timeframe name.
For example, we get the string "PERIOD_H1" from the PERIOD_H1 constant returning only "H1" from the string.

To check the opening of a new bar, we need to compare the opening time of the current bar with the previously remembered one. If the values under check are not equal, then a new bar is opened.
Since this is not an indicator (where there is already a predefined array of the current symbol/period timeseries) but an EA, we need to create a function to get the bar opening time:

//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

Pass the index of the bar, whose opening time we want to get, to the function, copy the required data into the array and return the received data from the array. Since we need the time of only one bar, we define the array with the dimension of 1.

Now, using this function, let's implement a function that returns the flag of opening a new bar:

//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }

Compare the previous opening time of the zero bar with the current one obtained from the TimeOpenBar() function. If the compared values are not equal, then remember the new time for the next check and return the true new bar opening flag. If there is an error, or if the compared values are equal, return false — there is no new bar.

To receive data from Parabolic SAR and send values to the trailing function, we will write a function for receiving data by the indicator handle:

//+------------------------------------------------------------------+
//| Return Parabolic SAR data from the specified timeseries index    |
//+------------------------------------------------------------------+
double GetSARData(const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(ExtHandleSAR, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Here everything is exactly the same as in the function for obtaining the bar opening time: we get the value in an array with a dimension of 1 according to the index passed to the function and, if successfully received, we return the value from the array. In case of an error, return EMPTY_VALUE.

To set a StopLoss position, we need to check that the stop distance from the price is not within the limits set by the StopLevel symbol level. If the StopLoss price is closer to the price than is allowed by the StopLevel distance, then the stop position will not be set due to the "invalid stops" error. To avoid getting such errors, we need to check this distance before setting the stop position. There is another level - the freeze level (FreezeLevel), which indicates the distance from the price to the position stop (StopLoss or TakeProfit), within which the stop levels cannot be changed, since they are likely to be triggered. But in the vast majority of cases these levels are no longer used, and we will not check them here.

As for StopLevel levels, there is a nuance: if the level is set to 0, this does not mean that it is absent. This means that we are dealing with floating values of this level. They are most often equal to two spread values. Or sometimes three. Here we need to select values, as they depend on the server settings. To do this, we will make a custom parameter in the function for obtaining the StopLevel value. The function will pass the multiplier, by which the spread should be multiplied by symbol to get StopLevel in case StopLevel is set to zero. If StopLevel level is not zero, then this value is simply returned:

//+------------------------------------------------------------------+
//| Return StopLevel value of the current symbol in points           |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }


Implement the main trailing function:

//+------------------------------------------------------------------+
//| Trailing stop function by StopLoss price value                   |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
      
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }

The logic is simple: in a loop through the list of open positions in the terminal, select each successive position by its ticket, check that the symbol and magic number of the position correspond to the filter set for selecting positions, and check the conditions for shifting the StopLoss level. If the conditions are suitable, modify the stop level.

The function for checking the stop level modification criteria:

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol into price
   int    pos_profit_pt = 0;                          // position profit in points
   
//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
        pos_profit_pt=int((tick.bid - pos_open) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- short position
      case POSITION_TYPE_SELL :
        pos_profit_pt=int((pos_open - tick.ask) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- return 'false' by default
      default: break;
     }
//--- no matching criteria
   return false;
  }

The conditions are simple:

  1. if the stop level and the level the stop needs to be moved to are equal, then no modification is required - return false,
  2. if the stop level is closer to the price than allowed by the StopLevel level, it cannot be modified due to an error, return false,
  3. if the price has not yet traveled a sufficient distance after the last modification, it is too early to modify, since the trailing step has not been maintained, return false,
  4. if the price has not reached the specified profit in points, it is too early to modify — return false.

These simple rules are the basis for any trailing. If the criteria are met, the stop needs to be modified. If not, the conditions are checked the next time the function is called.

Let's write the function to modify the StopLoss price of a position by its ticket:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request={};
   MqlTradeResult    result ={};

//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

We select a position based on the ticket passed to the function, fill in the required fields of the request structure and send a trade order to the server. In case of an error, display a message with an error code in the journal and return false. More information about trading operations can be found in the MQL5 language documentation.

Let's implement the function for trailing a stop position based on Parabolic SAR values:

//+------------------------------------------------------------------+
//| StopLoss trailing function using the Parabolic SAR indicator     |
//+------------------------------------------------------------------+
void TrailingStopBySAR(const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- get the Parabolic SAR value from the first bar of the timeseries
   double sar=GetSARData(SAR_DATA_INDEX);
   
//--- if failed to obtain data, leave
   if(sar==EMPTY_VALUE)
      return;
      
//--- call the trailing function with the StopLoss price obtained from Parabolic SAR 
   TrailingStopByValue(sar, magic, trailing_step_pt, trailing_start_pt);
  }

First, get the indicator value from bar 1. If the value is not obtained, leave. If the value from Parabolic SAR was successfully received, send it to the stop trailing function by value.

Now let's set the created trailing for Parabolic SAR in the EA handlers.

In OnTick():

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR();
  }

At each opening of a new bar, the trailing function for Parabolic SAR will be called with default values - all positions opened on the symbol will be trailed, regardless of their magic number. The stop level of positions will be trailed from the moment of their opening exactly according to the indicator values without any trailing step and without taking into account the profit of positions in points.

In OnTradeTransaction():

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR();
  }

In order for the trailing to work when a position is opened, the trailing function is called in the handler, provided that a new deal has been added to the list of deals in the terminal. Without this handler, trailing will only be triggered when a new bar opens.

We have created a basic trailing stop function and created a trailing EA based on the Parabolic SAR indicator. We can compile the EA and, by running it on the chart, open a position and manage trailing by the Parabolic SAR value. The EA file is attached below.


Using Standard Library CTrade

MQL5 Standard Library is designed to make it easier for end users to develop programs. The library provides convenient access to MQL5 internal functions.

Let's take advantage of this opportunity and replace the StopLoss position modification function with the PositionModify() method of the CTrade class.

Let's look at the content of the position modification method:

//+------------------------------------------------------------------+
//| Modify specified opened position                                 |
//+------------------------------------------------------------------+
bool CTrade::PositionModify(const ulong ticket,const double sl,const double tp)
  {
//--- check stopped
   if(IsStopped(__FUNCTION__))
      return(false);
//--- check position existence
   if(!PositionSelectByTicket(ticket))
      return(false);
//--- clean
   ClearStructures();
//--- setting request
   m_request.action  =TRADE_ACTION_SLTP;
   m_request.position=ticket;
   m_request.symbol  =PositionGetString(POSITION_SYMBOL);
   m_request.magic   =m_magic;
   m_request.sl      =sl;
   m_request.tp      =tp;
//--- action and return the result
   return(OrderSend(m_request,m_result));
  }

and compare it with the stop position modification function written in the EA above:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request={};
   MqlTradeResult    result ={};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

There is no particular difference. In the trading class method, at the very beginning, there is a check for the flag of removing the EA from the chart. The function features no such check.
Instead of the IsStopped() standard function, we use the same-name trading class with the formal parameter:

//+------------------------------------------------------------------+
//| Checks forced shutdown of MQL5-program                           |
//+------------------------------------------------------------------+
bool CTrade::IsStopped(const string function)
  {
   if(!::IsStopped())
      return(false);
//--- MQL5 program is stopped
   PrintFormat("%s: MQL5 program is stopped. Trading is disabled",function);
   m_result.retcode=TRADE_RETCODE_CLIENT_DISABLES_AT;
   return(true);
  }

Here, if IsStopped() returns true, then first a message is displayed in the journal that the EA is removed from the chart and then the EA stop flag is returned.

The remaining differences in the trade function and class method are insignificant - they are the same thing, implemented differently. In the class method, the structures declared in the class header are cleared, but in the function, these structures are local and are declared when the function is called, while all fields are initialized with zeros. In the function, if there is an error sending a trade request, a message with an error code is immediately displayed. After that, the result is returned, while the OrderSend() function call is simply returned in the trading class method.

Let's make changes to the already implemented EA, saving it under the new name TrailingBySAR_02.mq5. Let's make it possible to test the trail in the strategy tester opening positions based on the values of the Parabolic SAR indicator, while moving the StopLoss levels of the opened positions based on the values of the same indicator.

Include the trading class file to the EA, declare the EA magic number in the inputs and declare the trading class instance in the global area. Besides, in the OnInit() handler, assign the magic number from the inputs to the trading class object:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_02.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com" 
#property version   "1.00"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include <Trade\Trade.mqh>    // replace trading functions with Standard Library methods

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input ulong             InpMagic          =  123;              // Magic Number

//--- global variables
int      ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle
double   ExtStepSAR=0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;              // Parabolic SAR maximum
CTrade   ExtTrade;                     // trading operations class instance

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the magic number to the trading class object
   ExtTrade.SetExpertMagicNumber(InpMagic);

//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);
   
//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }   
//--- successful
   return(INIT_SUCCEEDED);
  }


In the EA's OnTick() handler, add the code for opening positions according to Parabolic SAR in the strategy tester. Also, add passing the EA magic number to the trailing function:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
   if(MQLInfoInteger(MQL_TESTER))
     {
      //--- get Parabolic SAR data from bars 1 and 2
      double sar1=GetSARData(SAR_DATA_INDEX);
      double sar2=GetSARData(SAR_DATA_INDEX+1);
      
      //--- if the price structure is filled out and Parabolic SAR data is obtained
      MqlTick tick={};
      if(SymbolInfoTick(Symbol(), tick) && sar1!=EMPTY_VALUE && sar2!=EMPTY_VALUE)
        {
         //--- if Parabolic SAR on bar 1 is below Bid price, while on bar 2 it is above Bid price, open a long position
         if(sar1<tick.bid && sar2>tick.bid)
            ExtTrade.Buy(0.1);
         //--- if Parabolic SAR on bar 1 is above Ask, while on bar 2 it is below Ask price, open a long position
         if(sar1>tick.ask && sar2<tick.ask)
            ExtTrade.Sell(0.1);
        }
     }
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR(InpMagic);
  }


In the OnTradeTransaction() handler, complete passing the magic number to the trailing function:

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR(InpMagic);
  }

Using the magic number in the EA to open positions in the tester and trail their stops will allow you to check the filter operation by magic number.

In the universal trailing function, replace calling the modification function with calling the trading class method:

//+------------------------------------------------------------------+
//| Universal trailing stop function by StopLoss price value         |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
         
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ExtTrade.PositionModify(pos_ticket, value_sl, PositionGetDouble(POSITION_TP));
     }
  }

Now in order to modify a stop level, we call not a previously implemented function, but the PositionModify() method of the CTrade trading class. The difference between these calls lies in one parameter. If the function was written to modify only the StopLoss price of a position, then when specifying the parameters it was necessary to pass the ticket value of the position being modified and the new level of its stop level to the function. Now, three parameters need to be passed to the trading class method - in addition to the position ticket and the StopLoss level value, we also need to specify the TakeProfit level. Since the position has already been selected, and its TakeProfit value does not need to be changed, we pass the TakeProfit value to the method without changes directly from the properties of the selected position.

The previously implemented ModifySL() function has now been removed from the EA code.

Let's compile the EA and run it in the strategy tester on any symbol and any chart timeframe in "Every tick" mode:


As we can see, the stops of the positions are correctly trailed by the value of the first bar of the Parabolic SAR indicator.

The EA file is attached below.


Ready-made trailing in the EA "in a couple of strings"

And yet, despite the ease of creating trailing and using it in an EA, we would like not to have to write down all the functions required for its operation in each new EA every time, but simply do everything in the "Plug & Play" style.
This is possible in MQL5. To do this, we only need to write the plug-in file once, and then simply connect it to the desired EA and write it where we need to call the trailing functions.

Let's move the created functions of all trailings to one new file. Create a new plug-in file TrailingsFunc.mqh in the folder containing the EA. It is advisable to store all such files in a common folder for all included files \MQL5\Include\, or in a subfolder within that directory. However, for this test, it is sufficient to create a file directly in the folder with the EA and include it from there.

Press Ctrl+N in the editor and select a new included file:


In the next Wizard window, enter the name of the TrailingFunc file.
By default, the string for entering the file name already contains the root directory for included files - Include. In other words, the file will be created in this folder.
Then we can simply move it to the desired folder with the EA, or manually enter the path to the desired folder in the file name string (instead of Include\, enter Experts\ and then the path to the folder with test EAs, if one is used, followed by the name of the created file TrailingFunc):


Click Finish and create an empty file:

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+


Now we need to transfer all previously created functions from test EAs here:

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Simple trailing by value                                         |
//+------------------------------------------------------------------+
void SimpleTrailingByValue(const double value_sl, const long magic=-1, 
                           const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- price structure
   MqlTick tick={};
   
//--- in a loop by the total number of open positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
         
      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl  =PositionGetDouble(POSITION_SL);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }
//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol into price
   int    pos_profit_pt = 0;                          // position profit in points
   
//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
        pos_profit_pt=int((tick.bid - pos_open) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- short position
      case POSITION_TYPE_SELL :
        pos_profit_pt=int((pos_open - tick.ask) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
          )
           return true;
        break;
        
      //--- return 'false' by default
      default: break;
     }
//--- no matching criteria
   return false;
  }
//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
      PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest    request={};
   MqlTradeResult     result ={};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = PositionGetString(POSITION_SYMBOL);
   request.magic     = PositionGetInteger(POSITION_MAGIC);
   request.tp        = PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = NormalizeDouble(stop_loss,(int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS));
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }
//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }
//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

The function for receiving data from the Parabolic SAR indicator was implemented in the EA. The data in this function was obtained by the specified indicator handle. This means that not only Parabolic SAR can act as such an indicator, but any other indicator suitable for use as prices for setting StopLoss positions.

Therefore, here this function is renamed into a general function for receiving data from indicators by handle:

//+------------------------------------------------------------------+
//| Return indicator data by handle                                  |
//| from the specified timeseries index                              |
//+------------------------------------------------------------------+
double GetIndData(const int handle_ind, const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(handle_ind, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Accordingly, there is now a trailing function based on the indicator data received from the above function:

//+------------------------------------------------------------------+
//| Trailing by indicator data specified by handle                   |
//+------------------------------------------------------------------+
void TrailingByDataInd(const int handle_ind, const int index=1, const long magic=-1, 
                       const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- get the Parabolic SAR value from the specified timeseries index
   double data=GetIndData(handle_ind, index);
   
//--- if failed to obtain data, leave
   if(data==EMPTY_VALUE)
      return;
      
//--- call the simple trailing function with the StopLoss price obtained from Parabolic SAR 
   SimpleTrailingByValue(data, magic, trailing_step_pt, trailing_start_pt, trailing_offset_pt);
  }

In the function, we first get data from the indicator by the specified handle from the specified bar index, and then call the trailing function by value, passing the StopLoss value received from the indicator to it.

Thus, we can trail stop levels of positions using data from any indicator suitable for this purpose.

Set the following function in the file in order not to include the creation of the Parabolic SAR indicator in the EA code:

//+------------------------------------------------------------------+
//| Create and return the Parabolic SAR handle                       |
//+------------------------------------------------------------------+
int CreateSAR(const string symbol_name, const ENUM_TIMEFRAMES timeframe, const double step_sar=0.02, const double max_sar=0.2)
  {
//--- set the indicator parameters within acceptable limits
   double step=(step_sar<0.0001 ? 0.0001 : step_sar);
   double max =(max_sar <0.0001 ? 0.0001 : max_sar);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ResetLastError();
   int handle=iSAR(symbol, period, step, max);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  symbol, TimeframeDescription(period), step, max, GetLastError());
     } 
//--- return the result of creating the indicator handle
   return handle;
  }

Essentially, the CreateSAR() function is a code moved from the OnInit() handler of the TrailingBySAR_01.mq5 test EA. This approach will allow us to simply call this function without having to write input variable correction strings for the indicator in the EA and create its handle.

Further down the code are similar functions for creating various moving averages, for example, the function for creating Adaptive Moving Average:

//+------------------------------------------------------------------+
//| Create and return Adaptive Moving Average handle                 |
//+------------------------------------------------------------------+
int CreateAMA(const string symbol_name, const ENUM_TIMEFRAMES timeframe,
              const int ama_period=9, const int fast_ema_period=2, const int slow_ema_period=30, const int shift=0, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
//--- set the indicator parameters within acceptable limits
   int ma_period=(ama_period<1 ? 9 : ama_period);
   int fast_ema=(fast_ema_period<1 ? 2 : fast_ema_period);
   int slow_ema=(slow_ema_period<1 ? 30 : slow_ema_period);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ::ResetLastError();
   int handle=::iAMA(symbol, period, ma_period, fast_ema, slow_ema, shift, price);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    symbol, TimeframeDescription(period), ma_period, fast_ema, slow_ema,
                    ::StringSubstr(::EnumToString(price),6), ::GetLastError());
     }
//--- return the result of creating the indicator handle
   return handle;
  }

All other functions are similar to the one presented above. There is no need to consider them here. You can find them in the TrailingsFunc.mqh file attached below.

These functions are designed to quickly create moving averages so that their data can be used instead of Parabolic SAR data when conducting our own studies aimed at creating various types of trailing.


To the test the implemented functions, create a test EA TrailingBySAR_03.mq5 and include the newly created TrailingsFunc.mqh file to it.
In the global area, declare the variable for storing the handle of the created indicator and assign the result of creating the ParabolicSAR indicator to this variable in the OnInit() handler:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_03.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include "TrailingsFunc.mqh"

//--- input parameters
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input long              InpMagic          =  123;              // Expert Magic Number

//--- global variables
int   ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create Parabolic SAR handle
   ExtHandleSAR=CreateSAR(Symbol(), InpTimeframeSAR, InpStepSAR, InpMaximumSAR);
   
//--- if there is an error creating the indicator, exit OnInit with an error
   if(ExtHandleSAR==INVALID_HANDLE)
      return(INIT_FAILED);

//--- successful
   return(INIT_SUCCEEDED);
  }


Now we only need to restart trailing according to the data of the created indicator in the OnTick() and OnTradeTransaction() handlers passing position magic number, specified in the EA settings, to the trailing function:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }

The created EA is a trailing stop based on the Parabolic SAR indicator data. It will trail the stop levels of positions opened on the symbol, on which this EA is launched, and whose magic number matches the one set in the EA settings.


Connecting trailing to EA

Finally, let's connect the trailing by Parabolic SAR to the standard ExpertMACD EA located in \MQL5\Experts\Advisors\ExpertMACD.mq5.

Save it as ExpertMACDPSAR.mq5 and make changes to include trailing.

In the global area, include the trailing function file, add trailing inputs and declare a variable to store the handle created by Parabolic SAR:

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include "TrailingsFunc.mqh"

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

//--- inputs for trail
input group  " - PSAR Trailing Parameters -"
input bool   InpUseTrail       =  true;      // Trailing is Enabled
input double InpSARStep        =  0.02;      // Trailing SAR Step
input double InpSARMaximum     =  0.2;       // Trailing SAR Maximum
input int    InpTrailingStart  =  0;         // Trailing start
input int    InpTrailingStep   =  0;         // Trailing step in points
input int    InpTrailingOffset =  0;         // Trailing offset in points
//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
int     ExtHandleSAR;
//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+


In the OnInit() handler, create an indicator and write its handle to the variable:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trail
   if(InpUseTrail)
     {
      ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT,InpSARStep,InpSARMaximum);
      if(ExtHandleSAR==INVALID_HANDLE)
         return(INIT_FAILED);
     }
   
//--- Initializing expert
//...
//...


In the OnDeinit() handler, delete the handle and free up the calculation part of the indicator:

//+------------------------------------------------------------------+
//| Deinitialization function of the expert advisor                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtExpert.Deinit();
   IndicatorRelease(ExtHandleSAR);
  }


In the OnTick() and OnTrade() handlers, launch trailing by Parabolic SAR indicator:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }
//+------------------------------------------------------------------+
//| Function-event handler "trade"                                   |
//+------------------------------------------------------------------+
void OnTrade(void)
  {
   ExtExpert.OnTrade();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }

This is all that needs to be added to the EA file in order to add a full-fledged trailing for Parabolic SAR to it.

Here, in the same EA, we can create a handle for another indicator, for example, a moving average, and pass its handle to the trailing function. In this case, we get trailing based on the moving average. We can combine the values obtained from different indicators and send the total calculated values to the trailing function, or, in different market situations, trail stops using different algorithms, passing the required calculated value of position stops to the trailing function. There is a lot of room for experimentation.

It is important to remember that the functions presented in the TrailingFunc.mqh file allow creating

  1. custom non-trading trailing EAs based on various algorithms or indicator values,
  2. include various trailing stops to existing trading EAs, working either according to indicator values or according to their own algorithms.

But there are also limitations: we cannot simultaneously pass different values to the trailing function for long and short positions, as well as we cannot trail positions opened on another symbol. There are also some inconveniences when connecting trailing - it is necessary to create indicators in the EA and store their handles in variables. We can get rid of everything described above, as well as of some other things, by creating trailing classes. I will consider that in the next article.

Let's run the created EA on a single pass in the tester with the set parameters.

Select the following settings:

  • Symbol: EURUSD,
  • Timeframe: M15,
  • Test on the last year on every tick without execution delays.

Input settings:


After testing at a given interval with the trailing turned off, we get the following statistics:



Now let's launch the EA, enabling trailing in the settings:


It is clear that after the trailing the chart became a little smoother. I invite readers to try out various solutions for testing trailing on their own.

All files are attached to the article for independent study and tests.


Conclusion

We have learned how to quickly create and connect a trailing stop to EAs. All we need to do to include a trailing stop to any EA is:

  1. place the TrailingsFunc.mqh include file to the EA folder,
  2. include this file to the EA file using the #include "TrailingsFunc.mqh" command,
  3. add creating the ParabolicSAR indicator to EA's OnInit(): ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT);
  4. add trailing call to the OnTick() and (if necessary) OnTrade() or OnTradeTransaction() EA handlers: TrailingByDataInd(ExtHandleSAR);
  5. release the calculation part of the ParabolicSAR indicator with IndicatorRelease(ExtHandleSAR) in EA's OnDeinit().


Now this EA will have a fully-fledged trailing stop built in to manage its positions. We can do trailing not only by the Parabolic SAR indicator, but also by any other indicator. We can also create custom algorithms for calculating stop loss levels.

We can use several trailings with different parameters in one EA and switch between them depending on the market situation. In the next article, I will consider trailing classes that are free from the disadvantages of simple functions.



Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/14782

Attached files |
ExpertMACDPSAR.mq5 (14.04 KB)
Last comments | Go to discussion (7)
amrali
amrali | 14 May 2024 at 07:59

Thanks for the article,

But I wish you modify this to avoid truncation errors with fp numbers:

        pos_profit_pt= int ((tick.bid - pos_open) / Point ());              // calculate the profit of the position in points 

To:

        pos_profit_pt= (int) MathRound((tick.bid - pos_open) / Point ());              // calculate the profit of the position in points 
Ivan Titov
Ivan Titov | 11 Aug 2024 at 18:36
The article is thorough, but it is too voluminous for a relatively small topic. Not everyone will be able to cope with it.
Roman Shiredchenko
Roman Shiredchenko | 11 Aug 2024 at 19:13
But everything is explained from start to finish... It is possible to connect other indicators for trawl, like MA for example.
Roman Shiredchenko
Roman Shiredchenko | 3 Sep 2024 at 15:00
Artyom Trishkin #:

You're welcome. There will be an article on trailing classes coming out soon - as a logical conclusion to this topic.

They will be used, let's say, more correctly and, in my opinion, more conveniently.

thank you - I have taken the examples to my robots:

//--- если не новый бар - уходим из обработчика
   if(!IsNewBar())
      return;
и 
//--- устанавливаем в объект торгового класса магический номер
   ExtTrade.SetExpertMagicNumber(InpMagic);

Roman Shiredchenko
Roman Shiredchenko | 4 Sep 2024 at 05:05

I'm looking at such a topic myself....

//+------------------------------------------------------------------+
//| Manage Open Positions: Trailing Stop   |
//+------------------------------------------------------------------+
void ManageOpenPositions(string Sym, int mn)
  {
   if(TrailingStop > 0)
    for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetSymbol(i)==Sym) // Select and check if the position is on the current symbol
      if(PositionGetString(POSITION_SYMBOL) == Sym)
      if(PositionGetInteger(POSITION_MAGIC)==mn || mn == -1)
        {
         ulong  ticket = (ulong)PositionGetInteger(POSITION_TICKET);      // Get the position ticket
...

I will also look at your proposed variant in the work:

//+------------------------------------------------------------------+
//| Функция трейлинга стопа по значению цены StopLoss                |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- структура цен
   MqlTick tick={};
//--- в цикле по общему количеству открытых позиций
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- получаем тикет очередной позиции
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- получаем символ и магик позиции
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- пропускаем позиции, не соответствующие фильтру по символу и магику
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- если цены получить не удалось - идём далее
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
...
Neural Networks Made Easy (Part 87): Time Series Patching Neural Networks Made Easy (Part 87): Time Series Patching
Forecasting plays an important role in time series analysis. In the new article, we will talk about the benefits of time series patching.
Creating a Trading Administrator Panel in MQL5 (Part II): Enhancing Responsiveness and Quick Messaging Creating a Trading Administrator Panel in MQL5 (Part II): Enhancing Responsiveness and Quick Messaging
In this article, we will enhance the responsiveness of the Admin Panel that we previously created. Additionally, we will explore the significance of quick messaging in the context of trading signals.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Example of Stochastic Optimization and Optimal Control Example of Stochastic Optimization and Optimal Control
This Expert Advisor, named SMOC (likely standing for Stochastic Model Optimal Control), is a simple example of an advanced algorithmic trading system for MetaTrader 5. It uses a combination of technical indicators, model predictive control, and dynamic risk management to make trading decisions. The EA incorporates adaptive parameters, volatility-based position sizing, and trend analysis to optimize its performance across varying market conditions.