English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Cómo crear su propio Trailing Stop

Cómo crear su propio Trailing Stop

MetaTrader 5Indicadores | 19 diciembre 2013, 08:00
10 031 2
Dmitry Fedoseev
Dmitry Fedoseev

Introducción

Antes de empezar con el tema de este artículo, creo que es una buena idea poner los puntos sobre las íes. Una vez más definimos los términos "posición" y "orden":

  • Posición; es un compromiso de trading, por ejemplo el número de contratos de comprados o vendidos de un instrumento financiero. Solo puede haber una posición por instrumento.
  • Orden; es una instrucción que se da a un broker para comprar o vender un instrumento financiero. Hay distintos tipos de órdenes: de mercado y pendientes, así como las órdenes de parada (Stop Loss y Take Profit).

Figura 1. Posiciones y órdenes.

Figura 1. Posiciones y órdenes.

Este artículo se centra en el nivel Trailing Stop Loss para las posiciones. Esta operación no tiene sentido para las órdenes pendientes, ya que nos podemos mover directamente hacia el precio de la orden. Y cuando se convierte en una posición (o parte de ella), este material sería muy útil.

Se puede cerrar la posición de la operación de trading, pulsando el botón "Cerrar" de la ventana de la posición (Figura 2).

Figura 2. Cierre de una posición mediante el botón "Cerrar" de la ventana de la posición.

Figura 2. Cierre de una posición mediante el botón "Cerrar" de la ventana de la posición. 1 - abra el menú contextual, 2 - seleccione "Cerrar posición" 
3 - Haga clic en el botón "Cerrar".

Pero además, se puede cerrar la posición automáticamente cuando el precio alcanza un determinado nivel de beneficio (Take Profit), o nivel de pérdida (Stop Loss). A diferencia del cierre de posición mediante el botón "Cerrar", el cierre con Stop Loss y Take Profit no se hace desde el terminal (por el trader o el experto), sino por el broker. Por lo tanto, el cierre de la posición está totalmente garantizado, independientemente de la conexión de Internet y del suministro eléctrico. Esto hace que el uso de Stop Loss sea prácticamente obligatorio en el funcionamiento del trading.

La única acción que tiene que hacer el trader; es dar una orden al broker para establecer el nivel de Stop de protección. En otras palabras, debes establecer un Stop Loss en la posición (o abrir la posición con el ajuste de este nivel). El ajuste de Stop Loss se hace mediante el comando "Modificar" del menú contextual del terminal. Elija una posición, haga un clic derecho y elija "Modificar o Eliminar". En la ventana de la posición, tiene que introducir el nivel de Stop Loss y hacer clic en "Modificar" (Figura 3).

Figura 3. Ajuste del nivel Stop Loss de una posición.

Figura 3. Ajuste del nivel Stop Loss de una posición. 1 - abra el menú contextual, 2 - haga clic en "Modificar o Eliminar", 3 - introduzca el valor, 4 - haga clic en "Modificar".  

El nivel Stop Loss de la posición se muestra en el gráfico del precio justo con su nivel de apertura (Figura 4).

Figura 4. Posición con Stop Loss. El nivel está señalado con una línea roja, etiquetada con SL a su izquierda.

Figura 4. Posición con Stop Loss. El nivel está señalado con una línea roja, etiquetada con SL a su izquierda.

Además de establecer Stop Loss para una posición, puede también cambiar su valor periódicamente. Por ejemplo, puede arrastrarlo hacia arriba si cambia el precio en la dirección favorable, reduciendo así la posible pérdida. A este arrastre se le conoce como Trailing Stop.

Hay muchas opciones de Trailing Stop: puede simplemente arrastrar el Stop Loss por encima del precio a una determinada distancia. Puede no mover el Stop Loss de inmediato, sino esperar a que la posición alcance cierto beneficio y entonces lo mueve inmediatamente al nivel del punto muerto. Está opción es típica y está incluida en el terminal de cliente de MetaTrader 5. Para usar el Trailing Stop típico haga un clic derecho en la posición y seleccione "Trailing Stop" (Figura 5).

Figura 5. Activación del Trailing Stop en el terminal.

Figura 5. Activación del Trailing Stop en el terminal. 1 - abra el menú contextual, 2 - haga clic en "Trailing Stop", 3 - elija el valor (o introduzca un valor).
El comando para introducir el valor (Personalizar nivel...)
está al final del menú contextual y se ve en esta imagen.

Además del seguimiento directo de los precios, el Trailing Stop puede trabajar en función de algún indicador técnico. Por ejemplo, en función de los promedios móviles, esto permite no reaccionar a los cambios de precio a corto plazo, en función del indicador Ichimoku u otro más apropiado; e incluso en función del indicador SAR parabólico (Stop And Reverse) que no está diseñado en principio para este propósito. Ver la Figura 6.  

Figura 6. El Indicador SAR parabólico.

Figura 6. El Indicador SAR parabólico. 

Normalmente en el procedimiento de programación de MQL4, se ha creado el Trailing Stop como una función separada o se ha integrado en otras funciones. Por ejemplo, en el experto MACD Sample, incluido en MetaTrader 4, la función Trailing Stop está integrada en la función de cierre de órdenes del mercado.

for(cnt=0;cnt<total;cnt++)
  {
   OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES);
   if(OrderType()<=OP_SELL &&         // check for opened position 
      OrderSymbol()==Symbol())        // check for symbol
     {
      if(OrderType()==OP_BUY)         // long position is opened
        {
         // should it be closed?
         if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
            MacdCurrent>(MACDCloseLevel*Point))
           {
            OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
            return(0); // exit
           }
         // check for trailing stop
         if(TrailingStop>0) 
           {
             
            if(Bid-OrderOpenPrice()>Point*TrailingStop)
              {
               if(OrderStopLoss()<Bid-Point*TrailingStop)
                 {
                  OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
                  return(0);
                 }
              }
           }
        }
      else // go to short position
        {
         // should it be closed?
         if(MacdCurrent<0 && MacdCurrent>SignalCurrent && 
            MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
           {
            OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
            return(0); // exit
           }
         // check for trailing stop
         if(TrailingStop>0) 
           {
             
            if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
              {
               if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
                 {
                  OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
                  return(0);
                 }
              }
           }
        }
     }
  }

MQL5, como Lenguaje orientado a objetos ofrece muchas más posibilidades en el diseño de los expertos. Le permite crear clases polivalentes de uso múltiple que se pueden integrar de manera rápida y sencilla en casi cualquier experto. En este artículo vamos a desarrollar dicha clase.


1. Creación de la clase base de Trailing Stop

Como ya se mencionó, hay una gran variedad de Trailing Stop, pero todos tienen unas funcionalidades comunes:

  • Determinar el tipo (dirección) de la posición
  • Determinar el nivel Stop Loss de la posición.
  • Calcular el nuevo nivel Stop Loss
  • Comprobar si hace falta cambiar el nivel Stop Loss actual
  • Modificar el nivel Stop Loss de la posición.

El tipo de Trailing Stop determinará solo el valor calculado del nivel Stop Loss. Por lo tanto, la funcionalidad básica del Trailing Stop se incluirá en la clase base. Para la funcionalidad, que depende del Trailing Stop, se crearán subclases. La aplicación de los métodos de estas subclases se hará mediante métodos virtuales de la clase base.

Dado que estamos pensando utilizar indicadores técnicos, es necesario garantizar un funcionamiento estable de estos mismos para poder aplicárselos periódicamente. Para este propósito, usaremos el temporizador. También tenemos previsto activar/desactivar el Trailing Stop (cuando se usa la clase como parte del mecanismo de trading), y hacerlo mediante un botón objeto gráfico (cuando se usa la clase como parte de un experto adicional). Según estos requisitos de funcionalidad, la clase base tendrá el siguiente conjunto de elementos:

class CTrailingStop
  {
protected:
public:
   void CTrailingStop(){};
   void ~CTrailingStop(){};
   void Init(){};                   // Initialization of class
   bool StartTimer(){};             // Start timer
   void StopTimer(){};              // Stop timer
   void On(){};                     // Turn on trailing stop
   void Off(){};                    // Turn off trailing stop
   bool DoStoploss(){};             // Main method of controlling level of Stop Loss position
   void EventHandle(){};            // Method of processing chart events (pressing button to turn on trailing stop)
   void Deinit(){};                 // Deinitialization
   virtual bool Refresh(){};        // Refresh indicator
   virtual void Setparameters(){};  // Setting parameters and loading indicator
   virtual int Trend(){};           // Trend shown by indicator
   virtual double BuyStoploss(){};  // Stop Loss value for the Buy position
   virtual double SellStoploss(){}; // Stop Loss value for the Sell position
  };

Cuando se le hace una llamada, el método Init() aceptará los parámetros generales que no dependen del tipo de Trailing Stop utilizado. El método establecerá el modo de Trailing Stop y preparará las variables con algunos parámetros del mercado.

  • StartTimer(); se utiliza para iniciar el temporizador, necesario para el direccionamiento periódico de los indicadores y para mantenerlos en el caché del terminal.
  • StopTimer(); se utiliza para detener el temporizador al finalizar el funcionamiento del experto.
  • On(); para activar el Trailing Stop y establecer el botón en el modo pulsado (si se usa el botón).
  • Off(); para desactivar el Trailing Stop y establecer el botón en el modo no pulsado (si se usa el botón).
  • DoStopLoss(); método principal para controlar el nivel Stop Loss de la posición
  • EventHandle(); se utiliza para procesar evento gráficos, en particular para responder al botón activado/desactivado del Trailing Stop y activar y desactivar el Trailing Stop, dependiendo de la posición del botón.
  • Deinit(); se ejecuta al finalizar el trabajo del experto, permite obtener el identificador del indicador.
  • Refresh(); lleva a cabo la actualización de los valores del indicador. Este método es necesario para determinar los valores actuales del indicador, antes de calcular los valores de Stop Loss. Además, este método se usa por separado; se le llama periódicamente por el temporizador para que los indicadores sigan operativos.
  • SetParameters(); al ser llamado, recibe los parámetros del indicador, el indicador se carga con los parámetros indicados.
  • Trend(); método para averiguar la tendencia, se muestra mediante el indicador. Si el indicador muestra una dirección hacia arriba, devuelve el valor 1, y si es hacia abajo, devuelve el valor -1.
  • Los métodos BuyStoploss() y SellStoploss() devolverán los nuevos valores Stop Loss para las posiciones de compra y venta, calculados por el indicador. 

1.1. El método Init()

El método Init() es el primer método al que se llama después de crear una instancia de la clase. Recibe parámetros generales, independientemente del tipo de Trailing Stop: símbolo, período de tiempo, modo de Trailing Stop (por ticks o por barras), añadir o no añadir el indicador en el gráfico, y crear o no crear el botón. Después acepta las propiedades del botón: Coordenada X del botón, Coordenada Y del botón, color del botón, color de la leyenda del botón.

Los parámetros, necesarios para seguir funcionando, se almacenan en las variables de la clase. Además, cuando trabaja el método Init(), determina los principales parámetros del mercado que no cambian, necesarios para el Trailing Stop: el número de decimales después de la coma y el valor del punto. Y finalmente, en función del tipo de Trailing Stop, se crea el nombre del botón y su leyenda. Si se ha establecido que se va a utilizar, se crea. 

En la parte "protected" vamos a declarar todas las variables necesarias:

protected:
string m_symbol;             // symbol
ENUM_TIMEFRAMES m_timeframe; // timeframe
bool m_eachtick;             // work on each tick
bool m_indicator;            // show indicator on chart
bool m_button;               // show "turn on/turn off" button
int m_button_x;              // x coordinate of button
int m_button_y;              // y coordinate of button
color m_bgcolor;             // button color
color m_txtcolor;            // button caption color
int m_shift;                 // bar shift
bool m_onoff;                // turned on/turned off
int m_handle;                // indicator handle
datetime m_lasttime;         // time of trailing stop last execution
MqlTradeRequest m_request;   // trade request structure
MqlTradeResult m_result;     // structure of trade request result
int m_digits;                // number of digits after comma for price
double m_point;              // value of point
string m_objname;            // button name
string m_typename;           // name of trailing stop type
string m_caption;            // button caption

Ahora vamos a escribir el propio método Init():

//--- Trailing stop initialization method
void Init(string             symbol,
          ENUM_TIMEFRAMES timeframe,
          bool   eachtick  =   true,
          bool   indicator =  false,
          bool   button    =  false,
          int    button_x  =      5,
          int    button_y  =     15,
          color  bgcolor   = Silver,
          color  txtcolor  =   Blue)
  {
//--- set parameters
   m_symbol    = symbol;    // symbol
   m_timeframe = timeframe; // timeframe
   m_eachtick  = eachtick;  // true - work on each tick, false - false - work once per bar 
//--- set bar, from which indicator value is used
   if(eachtick)
     {
      m_shift=0; // created bar in per tick mode
     }
   else
     {
      m_shift=1; // created bar in per bar mode
     }
   m_indicator = indicator; // true - attach indicator to chart
   m_button    = button;    // true - create button to turn on/turn off trailing stop
   m_button_x  = button_x;  // x coordinate of button
   m_button_y  = button_y;  // y coordinate of button
   m_bgcolor   = bgcolor;   // button color
   m_txtcolor  = txtcolor;  // button caption color 
//--- get unchanged market history 
   m_digits=(int)SymbolInfoInteger(m_symbol,SYMBOL_DIGITS); // number of digits after comma for price
   m_point=SymbolInfoDouble(m_symbol,SYMBOL_POINT);         // value of point 
//--- creating button name and button caption
   m_objname="CTrailingStop_"+m_typename+"_"+symbol;        // button name
   m_caption=symbol+" "+m_typename+" Trailing";             // button caption 
//--- filling the trade request structure
   m_request.symbol=m_symbol;                               // preparing trade request structure, setting symbol
   m_request.action=TRADE_ACTION_SLTP;                      // preparing trade request structure, setting type of trade action
//--- creating button
   if(m_button)
     {
      ObjectCreate(0,m_objname,OBJ_BUTTON,0,0,0);                 // creating
      ObjectSetInteger(0,m_objname,OBJPROP_XDISTANCE,m_button_x); // setting x coordinate
      ObjectSetInteger(0,m_objname,OBJPROP_YDISTANCE,m_button_y); // setting y coordinate
      ObjectSetInteger(0,m_objname,OBJPROP_BGCOLOR,m_bgcolor);    // setting background color
      ObjectSetInteger(0,m_objname,OBJPROP_COLOR,m_txtcolor);     // setting caption color
      ObjectSetInteger(0,m_objname,OBJPROP_XSIZE,120);            // setting width
      ObjectSetInteger(0,m_objname,OBJPROP_YSIZE,15);             // setting height
      ObjectSetInteger(0,m_objname,OBJPROP_FONTSIZE,7);           // setting font size
      ObjectSetString(0,m_objname,OBJPROP_TEXT,m_caption);        // setting button caption 
      ObjectSetInteger(0,m_objname,OBJPROP_STATE,false);          // setting button state, turned off by default
      ObjectSetInteger(0,m_objname,OBJPROP_SELECTABLE,false);     // user can't select and move button, only click it
      ChartRedraw();                                              // chart redraw 
     }
//--- setting state of trailing stop
   m_onoff=false;                                                 // state of trailing stop - turned on/turned off, turned off by default 
  };

Ha podido observar que durante la creación del nombre y la leyenda del botón, se ha utilizado la variable m_typename, que no ha recibido ningún valor inicial. Se le asigna un valor en los constructores de subclases. Por lo tanto, cuando se utilizan distintos métodos, el Trailing Stop tendrá distintos valores, que corresponden al tipo de Trailing Stop utilizado. 

1.2. El método StartTimer()

El método StartTimer() inicia el temporizador general del experto.  

//--- Start timer
bool StartTimer()
  {
   return(EventSetTimer(1));
  };

Al utilizar un temporizador, hay que añadir la llamada al método Refresh() en la función OnTimer() para acceder periódicamente al indicador.

1.3. El método StopTimer()

El método StartTimer() para el temporizador del experto.  

//--- Stop timer
void StopTimer()
  {
   EventKillTimer();
  };

Cuando acaba el experto de trabajar, este método detiene el temporizador, si lo ha utilizado. Se hace la llamada a este método durante la ejecución del método de la clase Deinit().  

1.4. El método On()

El método On() pone en marcha el Trailing Stop. La puesta en marcha se hace asignando el valor true a la variable m_onoff. Si el botón está configurado para su utilización en la inicialización de la clase, entonces estará pulsado. 

//--- Turn on trailing stop
void On()
  {
   m_onoff=true; 
   if(m_button)
     { // if button is used, it is "pressed"
      if(!ObjectGetInteger(0,m_objname,OBJPROP_STATE))
        {
         ObjectSetInteger(0,m_objname,OBJPROP_STATE,true);
        }
     }
  }

1.5. El método Off()

El método Off() desactiva el Trailing Stop. Se desactiva mediante la asignación del valor false a la variable m_onoff. Si el botón está configurado para su utilización en la inicialización de la clase, entonces estará sin pulsar. 

//--- Turn off trailing stop
void Off()
  {
   m_onoff=false;
   if(m_button)
     { // if button is used, it is "depressed"
      if(ObjectGetInteger(0,m_objname,OBJPROP_STATE))
        {
         ObjectSetInteger(0,m_objname,OBJPROP_STATE,false);
        }
     }
  }

1.6. El método EventHandle()

Se hace la llamada al método EventHandle() desde la función OnChartEvent(), y por consiguiente adquiere todos los parámetros enviados a la función OnChartEvent().

//--- Method of tracking button state - turned on/turned off
void EventHandle(const int id,const long  &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_OBJECT_CLICK && sparam==m_objname)
     { // there is an event with button
      if(ObjectGetInteger(0,m_objname,OBJPROP_STATE))
        { // check button state
         On(); // turn on
        }
      else
        {
         Off(); // turn off
        }
     }
  }

Si ocurre un evento CHARTEVENT_OBJECT_CLICK y dicho evento ocurre con el botón llamado m_objname (se asigna el nombre del objeto con el cual se ha producido el evento a la variable sparam), a continuación, se ejecuta el método en función del estado del botón On() u Off().

1.7. El método Deinit()

Se debe hacer la llamada al método Deinit() cuando el experto haya terminado de trabajar. Este método detiene el temporizador, libera el identificador del indicador y elimina el botón, si se ha utilizado. 

//--- Method of deinitialization
void Deinit()
  {
   StopTimer();                  // stop timer
   IndicatorRelease(m_handle);   // release indicator handle
   if(m_button)
     {
      ObjectDelete(0,m_objname); // delete button
      ChartRedraw();             // chart redraw
     }
  }

Más adelante, veremos los métodos Refresh(), SetParameters(), Trend(), BuyStoploss() y SellStoploss(); cuando vayamos a crear las subclases de Trailing Stop. Ahora vamos a abordar el método principal de la clase base; el método DoStoploss().

1.8. El método DoStoploss()

El método DoStoploss() se considera el método de funcionamiento principal, y se le debe llamar desde la función OnTick() con cada tick. Si el valor de la variable m_onoff es false (Trailing Stop desactivado), el método finaliza inmediatamente.  

if(!m_onoff)
  {
   return(true);// if trailing stop is turned off
  }

Además, si el método está trabajando en el modo de barras, se comprueba el tiempo, es decir, se compara el tiempo de la barra creada con el tiempo de la última ejecución exitosa de la función. Si los tiempos coinciden, el método ha finalizado.

datetime tm[1];
// get the time of last bar in per bar mode 
if(!m_eachtick)
  { 
   // if unable to copy time, finish method, repeat on next tick 
   if(CopyTime(m_symbol,m_timeframe,0,1,tm)==-1)
     {
      return(false); 
     }
   // if the bar time is equal to time of method's last execution - finish method
   if(tm[0]==m_lasttime)
     { 
      return(true);
     }
  }

Si el Trailing Stop esta activado y se hizo la comprobación del tiempo, entonces se ha ejecutado la parte principal del método, se actualizan los valores del indicador (se hace una llamada al método Refresh()).

if(!Refresh())
  { // get indicator values
   return(false);
  }

A continuación, en función del valor devuelto por el método Trend(), se ejecuta el Trailing Stop ya sea para las posiciones de compra o de venta.

// depending on trend, shown by indicator, do various actions
switch (Trend())
  {
   // Up trend
   case 1: 
      // code of trailing stop for the buy position
      break;
   // Down trend
   case -1: 
      // code of trailing stop for the sell position
      break;
  }

Considere su funcionamiento en el ejemplo de la posición de compra.

if(PositionSelect(m_symbol,1000))
  {   //--- select position. if succeeded, then position exists
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {//--- if position is buy

      //--- get Stop Loss value for the buy position
      sl=BuyStoploss(); 
      //--- find out allowed level of Stop Loss placement for the buy position
      double minimal=SymbolInfoDouble(m_symbol,SYMBOL_BID)-m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
      //--- value normalizing
      sl=NormalizeDouble(sl,m_digits); 
      //--- value normalizing
      minimal=NormalizeDouble(minimal,m_digits); 
      //--- if unable to place Stop Loss on level, obtained from indicator, 
      //    this Stop Loss will be placed on closest possible level
      sl=MathMin(sl,minimal); 
      //--- value of Stop Loss position
      double possl=PositionGetDouble(POSITION_SL); 
      //--- value normalizing
      possl=NormalizeDouble(possl,m_digits); 
      if(sl>possl)
        {//--- if new value of Stop Loss if bigger than current value of Stop Loss, 
         //    an attempt to move Stop Loss on a new level will be made
         //--- filling request structure
         m_request.sl=sl; 
         //--- filling request structure
         m_request.tp=PositionGetDouble(POSITION_TP); 
         //--- request
         OrderSend(m_request,m_result); 
         if(m_result.retcode!=TRADE_RETCODE_DONE)
           {//--- check request result
            //--- log error message
            printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode); 
            //--- unable to move Stop Loss, finishing
            return(false); 
           }
        }
     }
  }

Si se puede seleccionar la posición, se ha comprobado su tipo. Si el tipo de posición corresponde a la tendencia, entonces, mediante el método BuyStoploss(), obtenemos el valor requerido de Stop Loss (en la variable sl ). A continuación, se determina el nivel permitido, al cual hay que establecer el Stop Loss. Si el nivel calculado es muy parecido al permitido, ajustar el valor de la variable sl. Luego obtenemos el valor actual de la posición Stop Loss (en la variable possl ), y comparamos los valores de las variables sl y possl. Si el nuevo valor de Stop Loss es mejor que el valor actual; se modifica la posición.

Antes de hacer la modificación, se rellenan los campos sl y tp de la estructura MqlTradeRequest. Se le asigna a la variable m_request.sl el valor requerido de Stop Loss y a la variable m_request.tp  el valor existente de Take Profit (no hay que cambiarlo). Se rellena el resto de campos durante la ejecución del método Init(). Después de rellenar la estructura se hace una llamada a la función OrderSend().

Al finalizar su trabajo, se comprueba el valor de la variable m_result.retcode. Si el valor no es igual a TRADE_RETCODE_DONE, entonces por alguna razón no fue capaz de llevar a cabo la acción requerida por la función OrderSend(). Al mismo tiempo, sale un mensaje del registro con el número del error y se completa la ejecución del método. Si la función OrderSend() se finaliza con éxito, entonces hay que recordar el tiempo de la barra, en la que se realizó el último funcionamiento del método DoStoploss(). En caso de error, incluso en el modo de barras, se volverá a intentar poner en marcha el método con el siguiente tick. Se continuará con los intentos hasta completar el trabajo con éxito.

A continuación se muestra el código completo del método DoStopLoss().

bool DoStoploss()
  {
//--- if trailing stop is turned off
   if(!m_onoff)
     {
      return(true);
     }
   datetime tm[1];
//--- get the time of last bar in per bar mode
   if(!m_eachtick)
     {
      //--- if unable to copy time, finish method, repeat on next tick 
      if(CopyTime(m_symbol,m_timeframe,0,1,tm)==-1)
        {
         return(false);
        }
      //--- if the bar time is equal to time of method's last execution - finish method
      if(tm[0]==m_lasttime)
        {
         return(true);
        }
     }
//--- get indicator values
   if(!Refresh())
     {
      return(false);
     }
   double sl;
//--- depending on trend, shown by indicator, do various actions
   switch(Trend())
     {
      //--- Up trend
      case 1:
         //--- select position. if succeeded, then position exists
         if(PositionSelect(m_symbol))
           {
            //--- if position is buy
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               //--- get Stop Loss value for the buy position
               sl=BuyStoploss();
               //--- find out allowed level of Stop Loss placement for the buy position
               double minimal=SymbolInfoDouble(m_symbol,SYMBOL_BID)-m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
               //--- value normalizing
               sl=NormalizeDouble(sl,m_digits);
               //--- value normalizing
               minimal=NormalizeDouble(minimal,m_digits);
               //--- if unable to place Stop Loss on level, obtained from indicator, 
               //    this Stop Loss will be placed on closest possible level
               sl=MathMin(sl,minimal);
               //--- value of Stop Loss position
               double possl=PositionGetDouble(POSITION_SL);
               //--- value normalizing
               possl=NormalizeDouble(possl,m_digits);
               //--- if new value of Stop Loss if bigger than current value of Stop Loss, 
               //    an attempt to move Stop Loss on a new level will be made
               if(sl>possl)
                 {
                  //--- filling request structure
                  m_request.sl=sl;
                  //--- filling request structure
                  m_request.tp=PositionGetDouble(POSITION_TP);
                  //--- request
                  OrderSend(m_request,m_result);
                  //--- check request result
                  if(m_result.retcode!=TRADE_RETCODE_DONE)
                    {
                     //--- log error message
                     printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode);
                     //--- unable to move Stop Loss, finishing
                     return(false);
                    }
                 }
              }
           }
         break;
         //--- Down trend
      case -1:
         if(PositionSelect(m_symbol))
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               sl=SellStoploss();
               //--- adding spread, since Sell is closing by the Ask price
               sl+=(SymbolInfoDouble(m_symbol,SYMBOL_ASK)-SymbolInfoDouble(m_symbol,SYMBOL_BID));
               double minimal=SymbolInfoDouble(m_symbol,SYMBOL_ASK)+m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
               sl=NormalizeDouble(sl,m_digits);
               minimal=NormalizeDouble(minimal,m_digits);
               sl=MathMax(sl,minimal);
               double possl=PositionGetDouble(POSITION_SL);
               possl=NormalizeDouble(possl,m_digits);
               if(sl<possl || possl==0)
                 {
                  m_request.sl=sl;
                  m_request.tp=PositionGetDouble(POSITION_TP);
                  OrderSend(m_request,m_result);
                  if(m_result.retcode!=TRADE_RETCODE_DONE)
                    {
                     printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode);
                     return(false);
                    }
                 }
              }
           }
         break;
     }
//--- remember the time of method's last execution
   m_lasttime=tm[0];
   return(true);
  }

Observe las diferencias en el código de las posiciones de compra y de venta. Para la posición de compra, el valor que devuelve SellStopLoss() se incrementa con el valor del diferencial, ya que la posición de venta se ha cerrado con el precio Ask. Por consiguiente, el límite para el nivel mínimo de Stop Loss para comprar se obtiene mediante el precio Bid, y para vender mediante el precio Ask.

De momento hemos terminado con la creación de la clase base del Trailing Stop. Vamos a proceder con la creación de las subclases. 

2. Subclase Trailing Stop para el indicador SAR parabólico

Los métodos virtuales de la clase CTrailingStop nos dicen ya cuales son los contenidos de la subclase; los métodos SetParameters(), Refresh(), Trend(), BuyStoploss(), SellStoploss() y el constructor de la clase para definir el nombre del Trailing Stop. La clase se llamará CParabolicStop. Puesto que es una subclase de CTrailingStop, se indicará en su declaración.

class CParabolicStop: public CTrailingStop

Con esta declaración, al hacer una llamada a los métodos virtuales de la clase CParabolicStop se ejecutarán los métodos heredados de la clase base.  

Vamos a describir en detalle los métodos de la subclase.

2.1. El método CParabolicStop()

Este método tiene el mismo nombre que la propia clase, es un constructor. Se ejecuta automáticamente cuando se carga la clase, antes que los otros métodos de la clase. En el método CParabolicStop() se asigna el nombre del Trailing Stop a la variable m_typename. Se utiliza este método para crear el nombre y la leyenda del botón (en el método Init() de la clase base).

void CParabolicStop()
  {
   m_typename="SAR"; // define name of trailing stop type
  };

2.2. El método SetParameters()

El método SetParameters() acepta los parámetros del indicador cuando se le hace una llamada, y se carga el indicador con estos parámetros. Si se establece el parámetro m_indicator en el método Init() de la clase base, el indicador se añade al gráfico (la función ChartIndicatorAdd()).

// Method of setting parameters and loading the indicator
bool SetParameters(double sarstep=0.02,double sarmaximum=0.2)
  {
   m_handle=iSAR(m_symbol,m_timeframe,sarstep,sarmaximum); // loading indicator
   if(m_handle==-1)
     {
      return(false); // if unable to load indicator, method returns false
     }
   if(m_indicator)
     {
      ChartIndicatorAdd(0,0,m_handle); // attach indicator to chart
     }
    
   return(true);
  }

2.3. El método Refresh()

El método Refresh() obtiene el nuevo precio y actualiza los valores del indicador. En la parte "protected" de la clase hay una matriz pricebuf para el valor del precio y una matriz indbuf para los valores del indicador. El tamaño de ambas matrices es de un elemento, solo debe haber un valor de precio y un valor del indicador de la barra en proceso de formación o formada (dependiendo del parámetro m_shift, definido en la inicialización de la clase base).

// Method of getting indicator values
bool Refresh()
  {
   if(CopyBuffer(m_handle,0,m_shift,1,indbuf)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }
    
   if(CopyClose(m_symbol,m_timeframe,m_shift,1,pricebuf)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }    
   return(true); 
  }

2.4. El método Trend()

El método Trend() comprueba la posición del precio respecto a la línea del indicador. Si el precio está por encima de la línea, se trata de una tendencia alcista, y el método devuelve 1. Si el precio está por debajo de la línea, se trata de una tendencia bajista, y el método devuelve -1. No hay que excluir el caso (raro, pero posible) en el que el precio es igual a la línea del indicador. En este caso, se devuelve el valor 0.  

// Method of finding trend
int Trend()
  {
   if(pricebuf[0]>indbuf[0])
     { // price is higher than indicator line, up trend
      return(1);
     }
   if(pricebuf[0]<indbuf[0])
     { // price is lower than indicator line, down trend
      return(-1);
     }    
   return(0);
  }

2.5. Los métodos BuyStoploss() y SellStoploss()

Ya que el indicador parabólico dispone de una única línea, ambos métodos son idénticos. Devuelven el valor obtenido del método Refresh().

// Method of finding out Stop Loss level for buy
virtual double BuyStoploss()
  {
   return(indbuf[0]);
  };
// Method of finding out Stop Loss level for sell
virtual double SellStoploss()
  {
   return(indbuf[0]);
  };

Por ahora, el Trailing Stop está listo. De momento tiene una sola clase, pero se puede utilizar ya. Guárdelo como un archivo de inclusión por separado en la carpeta .\MQL5\Include, con el nombre Sample_TrailingStop.mqh (el archivo está adjunto al artículo). 

3. Añadir el Trailing Stop del indicador parabólico al Experto

Vamos a intentar añadir el Trailing Stop a algún experto, como My_First_EA del artículo Guía paso a paso para escribir un Asesor Experto en MQL5 para principiantes.

3.1. Abra el experto My_First_EA en MetaEditor y guárdelo como My_First_EA_SARTrailing.

3.2. Incluya el archivo de Trailing Stop. Añada esta línea a la parte superior del código del experto (preferiblemente antes de la declaración de las variables externas): 

#include <Sample_TrailingStop.mqh> // include Trailing Stop class

3.3. Después de las variables externas, cree una instancia de la clase CParabolicStop, llamada Trailing.

CParabolicStop Trailing; // create class instance 

 3.4. Inicialice la clase y defina los parámetros en la función OnInit(). Primero, declare las variables externas con los parámetros del indicador:

input double TrailingSARStep=0.02;
input double TrailingSARMaximum=0.2;

Y luego añada el código a la función OnInit().

Trailing.Init(_Symbol,PERIOD_CURRENT,true,true,false); // Initialize (set basic parameters)
if(!trailing.setparameters(TrailingSARStep,TrailingSARMaximum))
  { // Set parameters of used trailing stop type
   Alert("trailing error");
   return(-1);
  } 
Trailing.StartTimer(); // Start timer
Trailing.On();         // Turn on

3.5. Encuentre la función OnTimer() en el código del experto. La función OnTimer() no se usa en My_First_EA, así que añádala y, en ella, añada la llamada a Refresh(). 

void OnTimer()
  {
   Trailing.Refresh();
  }

3.6. Añada la llamada del método DoStoploss() a la parte superior de la función OnTick().

3.7. Compile el experto y trate de probarlo. Se muestran los resultados de la prueba del experto en la Figura 7 (sin Trailing Stop) y en la Figura 8 (con Trailing Stop).

Figura 7. Resultados de la prueba del Experto sin Trailing Stop.

Figura 7. Resultados de la prueba del Experto sin Trailing Stop.   

Figura 8. Resultados de la prueba del Experto con Trailing Stop.

Figura 8. Resultados de la prueba del Experto con Trailing Stop. 

La eficiencia del uso del Trailing Stop es obvia.

El archivo My_First_EA_SARTrailing.mq5 está adjunto al artículo.

4. Subclase Trailing Stop para NRTR

El indicador NRTR (Nick Rypock Trailing Reverse) por su nombre y representación gráfica (figura 9) se revela interesante para tratar de crear el Trailing Stop en él.

Figura 9. Indicador NRTR.

Figura 9. Indicador NRTR.

El indicador dibuja una línea de base (línea de soporte o resistencia) y la línea de meta. Cuando el precio es superior a la línea de meta, se mueve la línea de base en la dirección del movimiento del precio, se ignoran las pequeñas fluctuaciones del precio. Si el precio cruza la línea de base, se considera un cambio de tendencia, cambiando así la posición de la línea de base y la línea de meta en función del precio. El color de la línea de soporte y la línea de meta es azul en una tendencia alcista, y rojo en una tendencia bajista.

Cree otro Trailing Stop, ahora para el indicador NRTR.

Declare otra clase CNRTRStop incluida en la clase base CNRTRStop. 

class CNRTRStop: public CTrailingStop

El Trailing Stop para la subclase NRTR tendrá exactamente el mismo conjunto de métodos que el Trailing Stop para el indicador parabólico, excepto el constructor, que vamos a llamar CNRTRStop().  

4.1. El método CNRTRStop()

Ahora, en el constructor de la clase, se le asigna a la variable m_typename el valor NRTR según el indicador utilizado.

void CNRTRStop()
  {
   m_typename="NRTR"; // setting name of trailing stop type
  };

4.2. El método SetParameters()

Al hacer una llamada al método SetParameters(), este acepta los parámetros del indicador y se carga. Y a continuación, en función de los parámetros básicos del Trailing Stop, se añade el indicador al gráfico.

// Method of setting parameters and loading the indicator
bool SetParameters(int period,double k)
  {
   m_handle=iCustom(m_symbol,m_timeframe,"NRTR",period,k); // loading indicator
   if(m_handle==-1)
     { // if unable to load indicator, method returns false
      return(false); 
     }
   if(m_indicator)
     {
       
      ChartIndicatorAdd(0,0,m_handle); // attach indicator to chart
     }
   return(true);
  }

4.3. El método Refresh()

El método Refresh() copia dos buffers del indicador NRTR; el buffer de la línea de soporte y el buffer de la línea de resistencia.  

 // Method of getting indicator values
bool Refresh()
  {
   if(CopyBuffer(m_handle,0,m_shift,1,sup)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }
    
   if(CopyBuffer(m_handle,1,m_shift,1,res)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }
    
   return(true);
  }

Hay dos matrices declaradas en la parte "protected" de la clase: double sup[] y double res[].

protected:
double sup[1]; // value of support level
double res[1]; // value of resistance level

4.4. El método Trend()

El método Trend() comprueba cuál de las lineas existe actualmente. Si es la línea de soporte, esto significa que el indicador muestra una tendencia alcista. El propio método devuelve el valor 1. Si es la línea de resistencia, entonces el método devuelve -1. 

// Method of finding trend
int Trend()
  {
   if(sup[0]!=0)
     { // there is support line, then it is up trend
      return(1);
     }
   if(res[0]!=0)
     { // there is resistance line, then it is down trend
      return(-1);
     }
    
   return(0);
  }

4.5. El método BuyStoploss()

El método ButStoploss() devuelve el valor de la línea de soporte. 

// Method of finding out Stop Loss level for buy
double BuyStoploss()
  {
   return(sup[0]);
  }
4.6. El método SellStoploss()

El método SellStoploss() devuelve el valor de la línea de resistencia.  

// Method of finding out Stop Loss level for sell
double SellStoploss()
  {
   return(res[0]);
  }

Ahora, la clase Trailing Stop está completa. 

5. Añadir el Trailing Stop del indicador NRTR al Experto

Al igual que hicimos con el Trailing Stop del indicador parabólico, vamos a añadir el Trailing Stop de My_First_EA para NRTR al experto.

5.1. Abra el experto My_First_EA_SARTrailing en MetaEditor y guárdelo como My_First_EA_NRTRTrailing.

5.2. Sustituya los parámetros externos del Training Stop para el indicador parabólico por los parámetros del Trailing Stop para NRTR.

input int TrailingNRTRPeriod = 40;
input double TrailingNRTRK   =  2;

5.3. En lugar de crear una instancia de la clase CParabolicStop, cree una instancia de la clase CNRTRStop. Se encuentra el código a continuación de las variables externas. 

CNRTRStop Trailing; // create class instance 

5.4. En la función OnInit() sustituya los parámetros de llamada del método SetParameters() por los parámetros de NRTR.

Trailing.SetParameters(TrailingNRTRPeriod,TrailingNRTRK)

5.5. Compile el experto y trate de probarlo. 

Figura 10. Resultados de la prueba del Experto con Trailing Stop para NRTR

Figura 10. Resultados de la prueba del Experto con Trailing Stop para NRTR

El resultado del trabajo del Asesor Experto (Figura 10) con Trailing Stop no ha cambiado prácticamente en comparación con el trabajo del experto (Figura 7) sin Trailing Stop. El uso del Trailing Stop para el indicador parabólico ha resultado más eficiente para este experto. Se puede concluir que disponer de cierta cantidad de Trailing Stop puede resultar muy útil a la hora de desarrollar expertos, para experimentar et seleccionar el tipo de Trailing Stop más apropiado.  

El archivo My_First_EA_NRTRTrailing.mq5 está adjunto al artículo.

6. El Asistente Experto

Cuando se creó una clase base de Trailing Stop, esta fue pensada para controlar la activación del Trailing Stop mediante el botón. Vamos a crear un asistente experto para seguir las posiciones en los distintos símbolos, con diferentes tipos de Trailing Stop. El experto no abrirá posiciones, pero seguirá únicamente las que están abiertas.

6.1. Cree un experto en MetaEditor y llámelo Sample_TrailingStop.

6.2. Incluya el archivo Sample_TrailingStop.mqh. 

#include <Sample_TrailingStop.mqh> // include Trailing Stop class

6.3. Declare los parámetros externos de los indicadores.

input double SARStep=0.02;     // Step of Parabolic
input double SARMaximum=0.02;  // Maximum of Parabolic
input int NRTRPeriod=40;       // NRTR period
input double NRTRK=2;          // NRTR factor

6.4. Declare la matriz de símbolos, que puede utilizar el experto.

string Symbols[]={"EURUSD","GBPUSD","USDCHF","USDJPY"};

6.5. Declare las matrices para cargar las clases.

CParabolicStop *SARTrailing[];
CNRTRStop *NRTRTrailing[];

6.6. Cambie el tamaño de las matrices en la función OnInit() para cargar las clases en función del tamaño de la matriz Symbols     

ArrayResize(SARTrailing,ArraySize(Symbols));  // resize according to number of used symbols
ArrayResize(NRTRTrailing,ArraySize(Symbols)); // resize according to number of used symbols 

6.7. Cargue una instancia de la clase para cada elemento de la matriz en el bucle.

for(int i=0;i<ArraySize(Symbols);i++)
  { // for all symbols
   SARTrailing[i]=new CParabolicStop(); // create CParabolicStop class instance
   SARTrailing[i].Init(Symbols[i],PERIOD_CURRENT,false,true,true,5,15+i*17,Silver,Blue);    // initialization of CParabolicStop class instance 
   if(!SARTrailing[i].SetParameters(SARStep,SARMaximum))
     { // setting parameters of CParabolicStop class instance 
      Alert("trailing error");
      return(-1);
     }
   SARTrailing[i].StartTimer();         // start timer
//----
   NRTRTrailing[i]=new CNRTRStop();     // create CNRTRStop class instance
   NRTRTrailing[i].Init(Symbols[i],PERIOD_CURRENT,false,true,true,127,15+i*17,Silver,Blue); // initialization of CNRTRStop class instance
   if(!NRTRTrailing[i].SetParameters(NRTRPeriod,NRTRK))
     { // setting parameters of CNRTRcStop class instance 
      Alert("trailing error");
      return(-1);
     }
   NRTRTrailing[i].StartTimer();        // start timer 
  }

Nota: las coordenadas de los métodos de los botones se calculan al hacer la llamada a Init(). Los botones de Trailing Stop del indicador parabólico aparecen a la izquierda y los del indicador NRTR a la derecha.  

6.8. Añada una llamada al método DoStoploss() en la función OnTick() a cada instancia de Trailing Stop.

for(int i=0;i<ArraySize(Symbols);i++)
  {
   SARTrailing[i].DoStoploss();
   NRTRTrailing[i].DoStoploss();
  }

6.9. Añada el control de los eventos gráficos. 

void OnChartEvent(const int         id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam 
                  )
  {
   for(int i=0;i<ArraySize(Symbols);i++)
     {
      SARTrailing[i].EventHandle(id,lparam,dparam,sparam);
      NRTRTrailing[i].EventHandle(id,lparam,dparam,sparam);
     }
    
  }

6.10. En la función Deinit() desinicialice todas las instancias de la clase y bórrelas.

for(int i=0;i<ArraySize(Symbols);i++)
  {
   SARTrailing[i].Deinit(); 
   NRTRTrailing[i].Deinit();
   delete(SARTrailing[i]);  // delete object
   delete(NRTRTrailing[i]); // delete object
  }

Compile y añada el experto al gráfico. Los indicadores y los botones aparecen en el gráfico (Figura 11), el Asesor Experto está listo para funcionar.  

Figura 11. Botones e indicadores en el gráfico después de iniciar Sample_TrailingStop.

Figura 11. Botones e indicadores en el gráfico después de iniciar Sample_TrailingStop. 

Solo pulse el botón para seguir la posición correspondiente cuando se abra.

El archivo Sample_TrailingStop.mq5 está adjunto al artículo.

Conclusión

Vamos a repasar el orden de utilización de la clase CTrailingStop al crear un sistema de trading mecánico.

1. Incluya el archivo Sample_TrailingStop.mqh.

2. Declare las variables externas con los parámetros del indicador de Trailing Stop utilizado.

3. Cree la instancia de la clase.

4. Añada la llamada a los métodos Init(), SetParameters(), StartTimer() y On() desde la función OnInit().

5. Añada una llamada al método Refresh() desde la función OnTimer().

6. Añada una llamada al método DoStopLoss() desde la función OnTick().

7. Añada una llamada al método Deinit() desde la función OnDeinit(). 


Su Asesor Experto ya tiene una función de Trailing Stop, en 7 pasos y menos de 5 minutos!

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

Junqui
Junqui | 10 feb. 2017 en 16:15
Hola a todos.........bien, me pregunto ¿porqué esta clase no tiene OPERADORES DE AMBITO (::)?

Intento el método Parabolic SAR pero al hacer la prueba-atras veo la respuesta "solicitud inválida".
Jaime Gonzalez Ocampo
Jaime Gonzalez Ocampo | 23 mar. 2019 en 18:20

Hello, I need the code to stop the Trailing Stop, say, I have a position in buy the price I advance 10 pips and I want the TS to stop at 5 pips and not advance more if the price continued and is at 20 pips the TS is still in 5 pips, thanks
Creación de indicadores multicolor en MQL5 Creación de indicadores multicolor en MQL5
En este artículo, abordaremos la manera de crear indicadores multicolor o convertir los que ya existen en multicolor. MQL5 permite una representación práctica de los datos. Ya no hace falta ver a una docena de gráficos con indicadores y hacer al análisis del nivel RSI o Estocástico, es mejor colorear las velas de otros colores en función de los valores de los indicadores.
Jeremy Scott es un vendedor de gran éxito en el mercado MQL5 Jeremy Scott es un vendedor de gran éxito en el mercado MQL5
Jeremy Scott, más conocido en MQL5.community con el nick Johnnypasado, ha adquirido fama en el terreno de nuestro servicio de mercado MQL5. Ya ha ganado varios miles de dólares en el Mercado y este no es el límite, ni mucho menos. Hemos decidido estudiar atentamente al futuro millonario y preguntarle el secreto del éxito para los vendedores del mercado MQL5.
Distintas maneras para averiguar la tendencia en MQL5 Distintas maneras para averiguar la tendencia en MQL5
Cualquier trader daría lo que fuera por la posibilidad de determinar con precisión la tendencia en un momento dado. Es quizá el Santo Grial que busca todo el mundo. En este artículo abordaremos distintas maneras de detección de tendencias. Para ser más preciso -cómo programar distintas métodos clásicas para la detección de tendencias mediante MQL5.
Sobre nuevos raíles: Indicadores personalizados en MQL5 Sobre nuevos raíles: Indicadores personalizados en MQL5
No voy a enumerar todas las posibilidades y características de la nueva terminal y el lenguaje. Son numerosas y algunas de ellas merecen ser tratadas en un artículo separado. Tampoco hay aquí código escrito en lenguaje de programación orientado a objeto. Es un tema demasiado importante como para ser tan solo mencionado como ventaja adicional para los programadores. En este artículo vamos a ver los indicadores, su estructura, diseño, tipos y detalles de su programación al compararlos con MQL4. Espero que este artículo sea de utilidad tanto para principiantes como para programadores experimentados. Puede que algunos de estos últimos encuentren algo nuevo.