English Русский 中文 Deutsch 日本語 Português
preview
Cómo añadir Trailing Stop según el indicador Parabolic SAR

Cómo añadir Trailing Stop según el indicador Parabolic SAR

MetaTrader 5Ejemplos |
775 8
Artyom Trishkin
Artyom Trishkin

Contenido



Introducción

El trailing stop es una función con la que la mayoría de los tráders están familiarizados. Esta función está integrada en el terminal comercial MetaTrader 5 y ajusta automáticamente el nivel de Stop Loss, manteniéndolo a cierta distancia del precio actual:

Activación del Trailing Stop Estándar en MetaTrader 5

Un trailing stop es un desplazamiento automático de una posición de Stop Loss por detrás del precio, lo cual permite mantener el stop de protección a cierta distancia del precio en todo momento. Este enfoque permite al tráder proteger parte del beneficio acumulado sin salir de la posición antes de tiempo. Cada vez que el precio de mercado se aleja del precio de apertura de una posición, el trailing stop «tira» automáticamente el Stop Loss hacia arriba, manteniendo la distancia especificada entre este y el precio actual. Sin embargo, si el precio se acerca al precio de apertura, el Stop Loss se mantendrá en el mismo nivel. Esto ofrece protección contra las pérdidas derivadas de posibles fluctuaciones del mercado.

No obstante, si usted necesita una versión más especial de un trailing stop, siempre se puede desarrollar una función en MQL5 para ampliar las capacidades de la herramienta estándar.

Existe una función-programa a la que se le transmite el precio requerido para fijar el nivel de Stop Loss. El programa comprueba algunos factores de prohibición, como el StopLevel (la distancia más cercana a la que no se pueden colocar stops) o el FreezeLevel (la distancia de congelación dentro de la cual no se puede modificar una posición u orden pendiente). Es decir, si el precio se acerca más al nivel stop de la posición que al FreezeLevel, esperaremos que se active una orden stop y se prohíba su modificación. Los trailings tienen algunos ajustes de parámetros más individuales que también se comprueban antes de mover el nivel de Stop Loss al precio especificado, como el símbolo y número mágico de la posición. Todos estos criterios se comprueban justo antes de que la posición del Stop Loss se desplace al nivel especificado.

Los distintos tipos de trailing tienen diferentes algoritmos para calcular el precio de Stop Loss de una posición, que se transmiten a la función de trailing y con los que trabaja posteriormente la función de trailing stop.

Y este «puntero» de los niveles necesarios para el Stop Loss es tan bueno como debería ser el indicador Parabolic SAR.


El indicador Parabolic SAR (Stop and Reverse) es una herramienta popular en el análisis técnico que se utiliza para determinar los momentos de posible final e reversión de la tendencia actual. Este indicador fue desarrollado por Welles Wilder y se usa a menudo para el seguimiento automático del stop loss. Estas son las principales razones por las que el indicador Parabolic SAR resulta atractivo para arrastrar un stop de protección:

  1. Facilidad de interpretación: Parabolic SAR es fácil de interpretar, ya que se representa en el gráfico como puntos que se sitúan por encima o por debajo del precio. Cuando los puntos se encuentran por debajo de los precios, esto supone una señal de compra; cuando los puntos están por encima, esto supone una señal de venta.

  2. Seguimiento automático de precios: La principal ventaja de Parabolic SAR es su capacidad para adaptarse automáticamente a los cambios de precio y desplazarse en el tiempo. Esto lo hace ideal para establecer trailing stops, ya que protege los beneficios acercando el stop loss al precio actual a medida que se mueve la tendencia.

  3. Protección de los beneficios: A medida que el precio se desplaza en la dirección del beneficio de una posición abierta, Parabolic SAR «arrastra» el nivel de stop loss tras él, lo cual ayuda a proteger parte del beneficio acumulado contra un posible cambio de tendencia.

  4. Señales de salida: Además de la función de trailing stop, Parabolic SAR también puede servir como señal para cerrar una posición cuando los puntos del indicador cruzan el precio. Esto puede evitar más pérdidas cuando la tendencia cambia rápidamente.

  5. Sencillez de ajuste: Los parámetros de Parabolic SAR (paso y máximo) pueden ajustarse fácilmente para adaptarse a la volatilidad específica del mercado o a la estrategia comercial. Esto lo convierte en una herramienta versátil para una gran variedad de condiciones comerciales.

  6. Adecuado para todos los marcos temporales: El indicador puede usarse eficazmente en diferentes marcos temporales, por lo que resulta adecuado tanto para inversores a largo plazo como para tráders a corto plazo.

El uso de Parabolic SAR como trailing stop resulta especialmente útil en los mercados de tendencia, donde esta herramienta ayuda a maximizar los beneficios al tiempo que permite mantener la posición abierta mientras continúe la tendencia. Sin embargo, debemos recordar que durante los periodos de tendencia lateral o de baja volatilidad, el uso de Parabolic SAR puede provocar el cierre prematuro de posiciones debido a los frecuentes cambios en la posición de los puntos del indicador.


Trailing con Parabolic SAR

Vamos a echar un vistazo al esquema estructural de cualquier trailing.

Por lo general, el código del trailing stop consta de varios bloques autosuficientes que pueden separarse del esquema general y formalizarse como funciones:

  1. bloque de cálculo del nivel Stop Loss requerido; el valor obtenido se transfiere al bloque Trailing Stop Loss.
  2. bloque de Trailing Stop Loss; incluye
    1. una unidad de filtros
    2. un bloque de ajuste del Stop Loss sobre el valor recibido del bloque de cálculo del nivel de Stop Loss requerido; incluye
      1. un bloque de filtros que verifican que el servidor cumpla las condiciones de los niveles para un símbolo y que se cumplan las condiciones para el desplazamiento del Stop Loss.
      2. un bloque de modificación del valor de Stop Loss.

El bloque de cálculo del nivel de Stop Loss necesario es, en este caso concreto, el indicador Parabolic SAR. Su valor (normalmente desde la barra 1) en cada tick se envía al bloque de Trailing Stop Loss, donde en un ciclo por la lista de posiciones abiertas, cada posición seleccionada (sus propiedades) pasan por un bloque de filtros, normalmente según el símbolo y número mágico. Además, si se superan los filtros de símbolo/número mágico, el nivel de Stop Loss requerido se someterá a un filtrado adicional para comprobar que se cumplan las condiciones del nivel StopLevel del servidor, el paso de trailing, el valor del Stop Loss requerido en relación con su posición anterior y el criterio de inicio del trailing según el beneficio de la posición en pips. Si también se superan estos filtros, el Stop Loss de la posición se modificará para fijarse en un nuevo nivel.

Por lo tanto, ya tenemos un bloque para calcular el nivel de stop loss: es el indicador Parabolic SAR. Por ello, deberemos crear solo el bloque de desplazamiento de los niveles de Stop Loss de las posiciones seleccionadas según el símbolo actual y el ID del asesor experto (Magic Number). Si el valor de número mágico es -1, se buscará en todas las posiciones abiertas según un símbolo del gráfico. Si se especifica un número mágico, solo se buscarán las posiciones con el número mágico correspondiente. La función de trailing se iniciará solo cuando se abra una nueva barra o cuando se abra una nueva posición. Formalizaremos el ejemplo como asesor

En el asesor experto, crearemos un indicador Parabolic SAR con los parámetros especificados en la configuración del asesor experto. Los valores del indicador tomados de la barra especificada (por defecto, de la primera barra) se transmitirán a la función de trailing stop, que a su vez hará todos los cálculos necesarios para desplazar los niveles de Stop Loss de las posiciones. Tendremos que considerar un StopLevel en el símbolo más cerca de la distancia de la que no se pueden colocar stops. También se comprobará el nivel actual del Stop Loss ya establecido y si es el mismo, o mayor (para Buy), o menor (para Sell) que el nivel transmitido a la función, no será necesario desplazar el stop.

Todas estas comprobaciones serán gestionadas por una función especial que comprueba los criterios de modificación de una posición de Stop Loss. Solo después de haber realizado todas las comprobaciones necesarias, el stop de la posición se desplazará a un nuevo nivel mediante la función de modificación del stop.

Después, en la carpeta del terminal \MQL5\Experts\ crearemos un nuevo archivo del EA con el nombre TrailingBySAR_01.mq5.

En el segundo paso del wizard de nuevo archivo del EA, en la ventana que aparecerá, marcaremos el manejador OnTradeTransaction():


El manejador OnTradeTransaction() será necesario para iniciar un trailing cuando se abra una nueva posición.

El manejador se llamará cuando aparezca el evento TradeTransactiondonde también se producirá la apertura de una nueva posición.

Podrá obtener más información sobre las transacciones y eventos comerciales en el artículo "Por dónde comenzar a crear un robot comercial para la Bolsa de Moscú MOEX".

Luego añadiremos los parámetros de entrada y variables globales mencionados al archivo del EA creado:

//+------------------------------------------------------------------+
//|                                             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)
  {

  }

En el manejador OnInit() del EA, estableceremos los valores correctos para los parámetros del indicador introducidos en la configuración del indicador, crearemos el indicador y obtendremos su manejador:

//+------------------------------------------------------------------+
//| 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);
  }

Si hemos creado el indicador correctamente, se creará su manejador, según el cual obtendremos los valores de Parabolic SAR en adelante. Si se da un error de creación del indicador, en el registro se mostrará un mensaje de error con los datos del indicador que se está creando. Para describir el marco temporal cuyos datos se usan para crear el indicador, se utilizará la función que retorna la descripción textual del marco temporal:

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

Luego obtendremos la parte textual de la enumeración de la constante de marco temporal y como resultado obtendremos una cadena que contendrá solo el nombre del marco temporal.
Por ejemplo, de la constante PERIOD_H1 obtendremos la cadena "PERIOD_H1" , y de ella tomaremos y retornaremos solo "H1".

Para comprobar la apertura de una nueva barra, deberemos comparar la hora de apertura de la barra actual con la hora de apertura pasada memorizada previamente. Si los valores comprobados no son iguales, entonces significará que se ha dado la apertura de una nueva barra.
Como no se trata de un indicador, donde ya existe un array predefinido de series temporales del símbolo/periodo actual, sino de un asesor experto, necesitaremos crear una función para obtener la hora de apertura de la barra:

//+------------------------------------------------------------------+
//| 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];
  }

Después transmitiremos a la función el índice de la barra cuya hora de apertura queremos obtener, copiaremos los datos requeridos en un array y retornaremos los datos recibidos del array. Como solo necesitamos la hora de una barra, definiremos un array con una dimensionalidad igual a 1.

Ahora, usando esta función, escribiremos una función que devuelva la bandera para abrir una nueva barra:

//+------------------------------------------------------------------+
//| 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;
  }

A continuación, comparamos la hora de apertura transmitida de la barra cero con la actual obtenida de la función TimeOpenBar(). Si los valores comparados no son iguales, almacenaremos la nueva hora para la siguiente comprobación y retornaremos la bandera true para la apertura de una nueva barra. En caso de error, o si los valores comparados son iguales, retornaremos false, no hay nueva barra.

Para recibir los datos de Parabolic SAR y enviar los valores a la función de trailing, escribiremos una función para obtener los datos según el handle del indicador:

//+------------------------------------------------------------------+
//| 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];
  }

Aquí todo resulta exactamente igual que en la función de obtención de la hora de apertura de la barra: obtenemos el valor en un array con dimensionalidad 1 según el índice transmitido a la función y retornamos el valor del array si se ha obtenido con éxito. En caso de error, retornaremos el valor "vacío" EMPTY_VALUE.

Para fijar una posición de Stop Loss, deberemos comprobar que la distancia del stop respecto al precio no esté dentro de los límites fijados por el StopLevel del símbolo. Si el precio Stop Loss se encuentra más cerca del precio de lo permitido por la distancia StopLevel, no será posible establecer una posición stop: se generará el error "stops incorrectos". Para evitar que se produzcan dichos errores, deberemos comprobar esta distancia antes de fijar un stop de posición. Existe otro nivel, el FreezeLevel, que indica la distancia desde el precio hasta el stop de la posición (Stop Loss o TakeProfit), dentro de la cual no se pueden modificar los niveles stop, ya que es probable que se activen. Pero en la inmensa mayoría de los casos, estos niveles ya no se usan, y no vamos a hacer una prueba de ellos aquí.

En cuanto a los niveles StopLevel, existe un matiz a considerar: si el nivel se establece en 0, esto no significará su ausencia, sino los valores flotantes de este nivel, y la mayoría de las veces son iguales a dos valores de spread. A veces tres. Aquí deberemos ajustar los valores, ya que estos dependen de la configuración del servidor. Para ello, crearemos un parámetro personalizable en la función de obtención del valor StopLevel. Así transmitiremos a la función el multiplicador por el que se multiplicará la dispersión de los símbolos para obtener el nivel StopLevel en caso de que StopLevel esté a cero. Si StopLevel tiene un valor distinto de cero, simplemente se retornará ese valor:

//+------------------------------------------------------------------+
//| 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);
  }

Vamos a escribir la función básica de trailing:

//+------------------------------------------------------------------+
//| 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);
     }
  }

La lógica es sencilla: en un ciclo a través de la lista de posiciones abiertas en el terminal, seleccionaremos cada posición subsiguiente según su ticket, comprobaremos la correspondencia del símbolo y el número mágico de la posición con el filtro establecido para la selección de posiciones, y comprobaremos las condiciones para el desplazamiento del nivel de Stop Loss. Si las condiciones son adecuadas, modificaremos el stop.

Función para comprobar los criterios de modificación del stop:

//+------------------------------------------------------------------+
//|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;
  }

Las condiciones son sencillas:

  1. si el stop de la posición y el nivel al que debe desplazarse el stop son iguales, no será necesaria ninguna modificación, retornaremos false,
  2. si el nivel de stop está más cerca del precio de lo permitido por StopLevel, no podremos modificarlo, ya que obtendremos un error, y retornaremos false,
  3. si el precio aún no ha recorrido una distancia suficiente después de la última modificación, será demasiado pronto para modificar, porque el paso de trailing no está establecido, así que retornaremos false,
  4. si el precio no ha alcanzado el beneficio especificado en puntos, será demasiado pronto para modificar, así que retornaremos false.

Estas sencillas reglas son la base del funcionamiento de cualquier trailing. Si se cumplen los criterios, deberemos modificar el stop. Si no, las condiciones se comprobarán en la siguiente llamada a la función, y así sucesivamente.

Vamos a escribir una función para modificar el precio de Stop Loss de una posición según su 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;
  }

Primero seleccionaremos la posición según el ticket transmitido a la función, rellenaremos los campos necesarios de la estructura de la solicitud y enviaremos la orden comercial al servidor. Si se produce un error, enviaremos un mensaje al registro con el código de error y retornaremos false. Encontrará más detalles sobre las operaciones comerciales en la documentación de MQL5.

Como resultado, escribiremos la función de trailing stop de la posición según los valores del indicador Parabolic SAR:

//+------------------------------------------------------------------+
//| 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);
  }

Primero obtendremos el valor del indicador de la barra 1, si no obtenemos el valor, saldremos. Si el valor de Parabolic SAR se obtiene con éxito, lo enviaremos a la función de trailing stop según el valor.

Ahora vamos a escribir un trailing creado según Parabolic SAR en los manejadores del asesor experto.

En OnTick():

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

En cada apertura de una nueva barra, la función de trailing de Parabolic SAR se llamará con los valores por defecto: todas las posiciones abiertas en el símbolo serán arrastradas, independientemente de su número mágico. Las posiciones stop se arrastrarán desde el momento de su apertura exactamente según los valores del indicador sin ningún paso de trailing y sin tener en cuenta el beneficio de las posiciones en puntos.

En OnTradeTransaction():

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

Para que el trailing se active al abrir una posición, se llamará a la función de trailing en el manejador, siempre que se haya añadido una nueva transacción a la lista de transacciones del terminal. Sin este manejador, el trailing solo se activará cuando se abra una nueva barra.

Bueno, ya hemos creado una función básica de trailing stop y, usando esta como base, hemos hecho un asesor experto de trailing con la ayuda de Parabolic SAR. Ahora podemos compilar el asesor experto y, ejecutándolo en un gráfico, abrir una posición y controlar el funcionamiento del trailing según el valor de Parabolic SAR. El archivo del EA se adjunta al final del artículo para que el lector pueda estudiarlo por su cuenta.


Uso de la biblioteca estándar CTrade

La biblioteca estándar MQL5 está escrita en el lenguaje MQL5 y se ha diseñado para facilitar la escritura de programas a los usuarios finales. La biblioteca ofrece un cómodo acceso a la mayoría de las funciones internas de MQL5.

Vamos a aprovechar la oportunidad y a sustituir la función de modificación del StopLoss de una posición con el método PositionModify() de la clase CTrade.

Veamos el contenido del método de modificación de la posición:

//+------------------------------------------------------------------+
//| 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));
  }

y compararlo con la función de modificación de stops de la posición escrita en el EA anterior:

//+------------------------------------------------------------------+
//| 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;
  }

No hay mucha diferencia. En el método de la clase comercial, al principio hay una comprobación de la bandera de eliminación del EA del gráfico. En la función, no.
En este caso, no se utilizará la función estándar IsStopped(), sino el método homónimo de la clase comercial, pero con un parámetro formal:

//+------------------------------------------------------------------+
//| 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);
  }

Aquí, si la función IsStopped() retorna true, entonces primero se emitirá un mensaje en el registro indicando que el asesor experto se eliminará del gráfico y luego se retornará la bandera de parada del EA.

Las otras diferencias en la función comercial y el método de la clase son irrelevantes: suponen la misma cosa implementada de diferentes maneras. En el método de clase, las estructuras declaradas en el encabezado de la clase se borran, mientras que en una función estas estructuras son locales y se declararán al llamarse la función, con todos los campos inicializados con ceros. En la función, cuando una solicitud comercial se envía por error, se imprime inmediatamente un mensaje con el código de error y solo entonces se retornará el resultado, mientras que el método de la clase comercial simplemente retornará el resultado de la llamada OrderSend().

Vamos a implementar los cambios en el EA ya escrito, guardándolo con el nuevo nombre TrailingBySAR_02.mq5. En el asesor experto haremos posible probar el trailing en el simulador de estrategias, abriendo posiciones según los valores del indicador Parabolic SAR, mientras movemos los niveles Stop Loss de las posiciones abiertas según los valores del mismo indicador.

A continuación, conectaremos al asesor experto el archivo de la clase, declararemos en las variables de entrada el valor de número mágico del asesor, en la zona global, declararemos una instancia de la clase comercial, mientras qu ene l manejador OnInit() asignaremos al objeto de case comercial el valor de número mágico de las variables de entrada:

//+------------------------------------------------------------------+
//|                                             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);
  }

En el manejador OnTick() del asesor añadiremos el código para abrir las posiciones según Parabolic SAR en el simulador de estrategias, mientras que en la función de trailing añadiremos la transmisión del número mágico del asesor:

//+------------------------------------------------------------------+
//| 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);
  }

En el manejador OnTradeTransaction() también añadiremos la transmisión del número mágico a la función de trailing:

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

El uso del número mágico en el asesor experto para abrir posiciones en el simulador y arrastrar sus paradas nos permitirá comprobar el trabajo del filtro de número mágico.

En la función de trailing universal, sustituiremos la llamada de la función de modificación por la llamada de la clase comercial:

//+------------------------------------------------------------------+
//| 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));
     }
  }

Ahora, para modificar la posición del stop de la posición, se llamará no la función anteriormente escrita, sino el método PositionModify() de la clase comercial CTrade. La diferencia entre estas llamadas es un parámetro. Si la función se escribía para modificar únicamente el precio Stop Loss de una posición, era necesario transmitir el valor del ticket de la posición que se modificaba y el nuevo nivel de su stop a la función al especificar los parámetros. Ahora deberemos transmitir tres parámetros al método de la clase comercial, además del ticket de la posición y el valor del nivel de Stop Loss, también deberemos especificar el nivel de Take Profit. Como la posición ya está seleccionada y no es necesario cambiar su valor de Take Profit, transmitiremos el valor de Take Profit al método sin cambios directamente desde las propiedades de la posición seleccionada.

La función ModifySL() escrita anteriormente ha sido eliminada del código del EA.

Vamos a compilar el EA y ejecutarlo en el simulador de estrategias en cualquier símbolo y cualquier marco temporal del gráfico con el modelado "Todos los ticks":


Como podemos ver, los stops de las posiciones son correctamente arrastrados por el valor de la primera barra del indicador Parabolic SAR.

Si así lo desea, el lector podrá consultar el archivo del asesor al final del artículo.


Trailing listo para usar en un asesor experto "con solo un par de líneas"

Sin embargo, a pesar de lo sencillo que resulta crear un trailing y utilizarlo en un asesor experto, no querría escribir todas las funciones necesarias para su funcionamiento en cada nuevo asesor experto, sino simplemente hacerlo todo al estilo "Plug & Play".
Y eso es posible en MQL5. Para ello, solo tendremos que escribir un archivo de inclusión una vez, y luego simplemente conectarlo al asesor experto necesario y escribir la llamada de las funciones de trailing donde sea necesario.

Vamos a trasladar las funciones creadas de todas las funciones de trailing a un nuevo archivo. Para ello, crearemos un nuevo archivo plug-in llamado TrailingsFunc.mqh en la carpeta con el asesor experto. En general, resulta deseable almacenar todos estos archivos en una carpeta común para todos los archivos de inclusión \MQL5\Include\, o en una subcarpeta dentro de este directorio. Pero para esta prueba bastará con crear un archivo directamente en la carpeta con el asesor experto y conectarlo desde allí.

Luego pulsaremos Ctrl+N en el editor y seleccionaremos un nuevo archivo de inclusión:


En la siguiente ventana del wizard, introduciremos el nombre del archivo TrailingFunc que deseemos conectar.
Deberemos considerar que, por defecto, el directorio raíz de los archivos a incluir ya está introducido en la línea de nombre del archivo: Include. Es decir, el archivo se creará en esta carpeta.
A continuación, podremos simplemente moverlo a la carpeta necesaria con el asesor experto, o podremos escribir la ruta en la línea de nombre del archivo a la carpeta necesaria (en lugar de Include\ escribiremos Experts\ y luego la ruta a la carpeta con los asesores expertos de prueba, si los hay, y luego el nombre del archivo TrailingFunc que se creará):

Después haremos clic en "Finalizar" y crearemos un archivo vacío:

//+------------------------------------------------------------------+
//|                                                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
//+------------------------------------------------------------------+

Ahora todas las funciones previamente creadas de los asesores expertos de prueba deberán ser transferidas aquí:

//+------------------------------------------------------------------+
//|                                                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];
  }

Asimismo, hemos escrito una función en el asesor experto para obtener los datos del indicador Parabolic SAR. Los datos de esta función se han obtenido usando el manejador del indicador especificado. Esto significa que dicho indicador podría ser no solo Parabolic SAR, sino cualquier otro indicador adecuado para utilizarse como precio para fijar las posiciones de Stop Loss.

Por lo tanto, aquí esta función pasará a llamarse función general de obtención de datos de los indicadores según el 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];
  }

En consecuencia, ahora existirá una función de trailing basada en los datos de indicadores obtenidos a partir de la función anterior:

//+------------------------------------------------------------------+
//| 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);
  }

En la función primero obtendremos los datos del indicador según el manejador especificado desde el índice de barra indicado, y luego llamaremos a la función de trailing según valor, transmitiéndole el valor obtenido del indicador para el Stop Loss.

De este modo, podremos rastrear las paradas de la posición utilizando los datos de cualquier indicador adecuado para ello.

Para no incluir la creación del indicador Parabolic SAR en el código del EA, colocaremos dicha función en el archivo:

//+------------------------------------------------------------------+
//| 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;
  }

De hecho, la función CreateSAR() será el código transferido desde el manejador OnInit() del EA de prueba TrailingBySAR_01.mq5. Este enfoque nos permitirá simplemente llamar a esta función sin tener que escribir líneas en el asesor experto para corregir las variables de entrada para el indicador y crear su handle.

Más abajo en el código hay funciones similares para crear diferentes medias móviles, como la función para crear la Media Móvil Adaptativa:

//+------------------------------------------------------------------+
//| 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;
  }

Todas las demás funciones serán similares a la presentada anteriormente, y no las consideraremos aquí: podrá verlas en el archivo TrailingsFunc.mqh adjunto al artículo.

Estas funciones están diseñadas para crear rápidamente medias móviles y utilizar sus datos en lugar de los datos de Parabolic SAR cuando realicemos nuestra propia investigación para crear diferentes tipos de trailing.

Para probar las funciones escritas, crearemos un nuevo asesor experto de prueba con el nombre TrailingBySAR_03.mq5 y le conectaremos el archivo de inclusión TrailingsFunc.mqh que acabamos de crear.

En el área global declararemos una variable para almacenar el handle del indicador creado, y en el manejador OnInit() asignaremos el resultado de la creación del indicador ParabolicSAR a esta variable:

//+------------------------------------------------------------------+
//|                                             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);
  }

Y solo quedará iniciar el trailing según los datos del indicador creado en los manejadores OnTick() y OnTradeTransaction(), transmitiendo a la función de trailing el número mágico de la posición indicado en los ajustes del asesor experto:

//+------------------------------------------------------------------+
//| 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);
  }

El asesor experto creado será un trailing stop según los datos del indicador Parabolic SAR. Este iterará los stops de las posiciones abiertas en el símbolo en el que se está ejecutando este asesor experto, y que tengan un número mágico igual al establecido en la configuración del asesor experto.


Conectamos el trailing al EA

Por último, vamos a conectar el trailing según Parabolic SAR al asesor experto ExpertMACD del paquete estándar, con la ubicación \MQL5\Experts\Advisors\ExpertMACD.mq5.

Lo guardaremos con el nuevo nombre ExpertMACDPSAR.mq5 y lo modificaremos para conectar el trailing.

En el área global, conectaremos el archivo de funciones de trailing, añadiremos los parámetros de entrada del trailing y declararemos una variable para almacenar el handle de Parabolic SAR creado:

//+------------------------------------------------------------------+
//|                                                   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                            |
//+------------------------------------------------------------------+

En el manejador OnInit(), crearemos un indicador y escribiremos su manejador en una 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
//...
//...

En el manejador OnDeinit(), eliminaremos el manejador y liberaremos la parte del cálculo del indicador:

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

En los manejadores OnTick() y OnTrade(), ejecutaremos el trailing según el indicador Parabolic SAR:

//+------------------------------------------------------------------+
//| 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);
  }

Esto es todo lo que necesitamos añadir al archivo del EA para añadir un trailing completo según Parabolic SAR.

Aquí, en el propio asesor experto, podremos crear el manejador de otro indicador, por ejemplo, una media móvil, y transmitir su manejador a la función de trailing. En este caso obtendremos un trailing según la media móvil. Podemos combinar los valores recibidos de diferentes indicadores y enviar los valores calculados agregados a la función de trailing, o arrastrar los stops en diferentes situaciones de mercado utilizando diferentes algoritmos, transfiriendo el valor calculado necesario de los stops de la posición a la función de trailing. Tenemos mucho margen para la experimentación.

Recuerde: las funciones presentadas en el archivo TrailingFunc.mqh nos permiten crear

  1. nuestros propios asesores expertos de trailing no comerciales según diversos algoritmos o valores de indicadores,
  2. conectar a los asesores expertos de trading existentes varios sistemas de trailing que funcionan según los valores de los indicadores o según sus propios algoritmos.

Pero existen algunas limitaciones: no podemos transmitir diferentes valores a la función de trailing para arrastrar posiciones largas y cortas al mismo tiempo, y no podemos arrastrar posiciones abiertas en otro símbolo. También hay algunos inconvenientes al conectar el trailing: es necesario crear indicadores en el asesor experto y almacenar sus manejadores en variables. Podemos librarnos de todo lo anterior, y de un poco más, creando clases de trailing, pero eso será ya un tema del próximo artículo, donde analizaremos tales posibilidades.

Vamos a ejecutar el asesor experto creado para una sola pasada en el simulador con los parámetros establecidos.

El intervalo y la configuración de la prueba se seleccionarán de la forma siguiente:

  • Símbolo: EURUSD,
  • Marco temporal: M15,
  • Prueba el año pasado en todos los ticks sin ejecución retardada.

Configuración de los parámetros de entrada:


Tras realizar las pruebas en un intervalo determinado con el trailing desactivado, obtenemos estas estadísticas:



Ahora vamos a ejecutar el asesor experto habilitando el trailing en los ajustes:


Podemos ver que el programa es un poco más suave después del funcionamiento de trailing. Podemos sugerir a los lectores que prueben por su cuenta distintas soluciones para las pruebas de trailing.

Todos los archivos se adjuntan al artículo para que trabaje con ellos y los ponga a prueba por su cuenta.


Conclusión

Hoy hemos aprendido cómo crear y conectar rápidamente un trailing stop a los asesores expertos. Todo lo que necesitamos hacer para conectar un trailing stop a cualquier EA es:

  1. colocar el archivo de inclusión TrailingsFunc.mqh en la carpeta con el asesor experto,
  2. conectar este archivo al archivo del EA con el comando #include "TrailingsFunc.mqh",
  3. escribir la creación del indicador ParabolicSAR en OnInit() del asesor experto: ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT);
  4. escribir en los manejadores del asesor OnTick() y (si es necesario) en OnTrade() o OnTradeTransaction() la llamada del trailing: TrailingByDataInd(ExtHandleSAR);
  5. liberar en OnDeinit() del EA la parte del cálculo del indicador ParabolicSAR utilizando IndicatorRelease(ExtHandleSAR).


Este EA tendrá ahora un trailing stop incorporado para gestionar nuestras posiciones. Podemos crear trailing no solo en el indicador Parabolic SAR, sino también en cualquier otro indicador. También podemos crear nuestros propios algoritmos para calcular los niveles de stop loss.

Asimismo, será posible utilizar varias líneas de trailing con diferentes parámetros en un EA y alternar entre ellas dependiendo de la situación del mercado.  En el próximo artículo analizaremos las clases de trailing desprovistas de las desventajas de las funciones simples.


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/14782

Archivos adjuntos |
ExpertMACDPSAR.mq5 (14.04 KB)
Ivan Titov
Ivan Titov | 11 ago 2024 en 18:36
El artículo es completo, pero demasiado voluminoso para un tema relativamente pequeño. No todo el mundo podrá asimilarlo.
Roman Shiredchenko
Roman Shiredchenko | 11 ago 2024 en 19:13
Pero todo está explicado de principio a fin... Es posible conectar otros indicadores para arrastre, como MA por ejemplo.
Roman Shiredchenko
Roman Shiredchenko | 3 sept 2024 en 15:00
Artyom Trishkin #:

De nada. Pronto publicaremos un artículo sobre las clases de arrastre, como conclusión lógica de este tema.

Se utilizarán, digamos, de forma más correcta y, en mi opinión, más conveniente.

gracias - he llevado los ejemplos a mis robots:

//--- si no es una nueva barra - salir del manejador
   if(!IsNewBar())
      return;
и 
//--- establecer el número mágico en el objeto de clase de comercio
   ExtTrade.SetExpertMagicNumber(InpMagic);

Roman Shiredchenko
Roman Shiredchenko | 4 sept 2024 en 05:05

Yo mismo estoy estudiando ese tema....

//+------------------------------------------------------------------+
//| Gestión de Posiciones Abiertas: Trailing Stop
//+------------------------------------------------------------------+
void ManageOpenPositions(string Sym, int mn)
  {
   if(TrailingStop > 0)
    for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetSymbol(i)==Sym) // Seleccionar y comprobar si la posición está en el símbolo actual
      if(PositionGetString(POSITION_SYMBOL) == Sym)
      if(PositionGetInteger(POSITION_MAGIC)==mn || mn == -1)
        {
         ulong  ticket = (ulong)PositionGetInteger(POSITION_TICKET);      // Obtener el ticket de posición
...

También voy a mirar a su variante propuesta en el trabajo:

//+------------------------------------------------------------------+
//| Función StopLoss trailing por valor de precio StopLoss |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- estructura de precios
   MqlTick tick={};
//--- en el ciclo por número total de posiciones abiertas
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- obtener el siguiente ticket de posición
      ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==0)
         continue;
         
      //--- obtener símbolo y posición magik
      string pos_symbol = PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      
      //--- omitir posiciones que no coincidan con los filtros de símbolos y magick
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- si no se han obtenido los precios - seguir adelante
      if(!SymbolInfoTick(Symbol(), tick))
         continue;
...
vladimir-gm
vladimir-gm | 9 jul 2025 en 13:40

¿Puede alguien explicar en detalle, o publicar un código listo (o archivo)? de tal EA:


que hace esto Tral.

SIN ROBOT!

sl en constante movimiento en TODAS las posiciones abiertas MANUALMENTE.

Aprendizaje automático y Data Science (Parte 25): Predicción de series temporales de divisas mediante una red neuronal recurrente (RNN) Aprendizaje automático y Data Science (Parte 25): Predicción de series temporales de divisas mediante una red neuronal recurrente (RNN)
Las redes neuronales recurrentes (RNNs, Recurrent Neural Networks) destacan por aprovechar la información del pasado para predecir acontecimientos futuros. Sus notables capacidades predictivas se han aplicado en diversos ámbitos con gran éxito. En este artículo, utilizaremos modelos RNN para predecir tendencias en el mercado de divisas, demostrando su potencial para mejorar la precisión de las predicciones en el comercio de divisas.
Características del Wizard MQL5 que debe conocer (Parte 24): Medias móviles Características del Wizard MQL5 que debe conocer (Parte 24): Medias móviles
Las medias móviles son un indicador muy común que la mayoría de los operadores utilizan y comprenden. Exploramos posibles casos de uso menos comunes dentro de los Asesores Expertos disponibles en el Asistente de MQL5.
Redes neuronales: así de sencillo (Parte 88): Codificador de series temporales totalmente conectadas (TiDE) Redes neuronales: así de sencillo (Parte 88): Codificador de series temporales totalmente conectadas (TiDE)
El deseo de obtener las previsiones más exactas impulsa a los investigadores a aumentar la complejidad de los modelos de previsión. Lo que a su vez conlleva un aumento de los costes de entrenamiento y mantenimiento del modelo. Pero, ¿está esto siempre justificado? En el presente artículo, me propongo presentarles un algoritmo que explota la sencillez y rapidez de los modelos lineales y muestra resultados a la altura de los mejores con arquitecturas más complejas.
Optimización automatizada de parámetros para estrategias de negociación con Python y MQL5 Optimización automatizada de parámetros para estrategias de negociación con Python y MQL5
Existen varios tipos de algoritmos para la autooptimización de estrategias y parámetros de negociación. Estos algoritmos se utilizan para mejorar automáticamente las estrategias de negociación basándose en datos históricos y actuales del mercado. En este artículo veremos uno de ellos con ejemplos en Python y MQL5.