Experto comercial universal: Trabajando con trailing-stops personalizados (parte 6)

Vasiliy Sokolov | 5 julio, 2016

Índice


Introducción

Este material continúa con una serie de artículos dedicados a la descripción del llamado "Experto comercial universal", un conjunto especial de clases que proporcionan al usuario un motor comercial para implementar sus estrategias comerciales. En las partes anteriores se han descrito los diferentes módulos del motor, que permiten ampliar la funcionalidad básica de cualquier estrategia basada en él. Sin embargo, la mayoría de estos módulos o son parte de la clase CStrategy, o implementan objetos con los que esta clase ha trabajado directamente. 

En esta parte del artículo vamos a continuar desarrollando la funcionalidad del motor comercial CStrategy y estudiaremos una nueva capacidad: el soporte de trailing-stops. A diferencia de otros módulos de CStrategy, los algoritmos de trailing-stop son externos en relación al propio motor comercial. Esto significa que tanto su presencia como su ausencia no deberían influir en la funcionalidad del propio CStrategy. Esta propiedad se puede lograr mediante el uso de una técnica de programación especial llamada composición. Hablaremos aquí de cómo hacerlo. Además, continuaremos la tradición tácita de añadir un nuevo funcional exclusivamente con la ayuda de módulos o clases adicionales, siguiendo estrictamente los principios y la filosofía de la programación orientada a objetos.

 

Posibles implementaciones de la función de trailing

El trailing stop, en esencia, es un tipo de algoritmo cuya una única tarea es trasladar el stop-loss a un determinado nivel de precios, con el fin de proteger la posición contra una pérdida excesiva. Resulta obvio que la cantidad de algoritmos de control del stop-loss puede ser enorme. Por supuesto, sería posible proponer el algoritmo para la gestión de trailing-stop como un método independiente, ubicado en la clase CStrategy. Por ejemplo, podría tomar como parámetro la posición actual y retornar el nivel comercial al que sería necesario trasladar el stop-loss actual de la posición:

class CStrategy
   {
public:
   double TrailingMode1(CPosition* pos);
   };

Luego directamente en la estrategia se podría gestionar la posición de acuerdo con la función de trailing:

void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos)
  {
   double new_sl = TrailingMode1(pos);
   pos.StopLossValue(new_sl);
  }

Sin embargo, ya que las funciones de control del trailing-stop pueden ser muchas, no es nada deseable ubicarlas en la propia clase CStrategy. Y es que, como ya hemos dicho en la introducción, las funciones de trailing son externas con respecto al propio algorítmo básico del experto. Estas funciones no son imprescindibles para el funcionamiento del motor comercial, y su tarea es solo la de simplificar el proceso de negociación. Por eso, su ausencia no influye en modo alguno en el funcionamiento del propio CStrategy o de una estregia que no use en general trailing-stops. Por otra parte, la presencia de algoritmos de gestión del trailing-stop de una posición no deberá complicar la legibilidad del código, ni provocar el aumento del código de las clases básicas. Es por ello que todas las funciones de trailing-stop deben ser colocadas en clases y archivos separados, conectándolas a la estrategia de negociación según sean necesarias.

Como variante alternativa, podríamos implementar una función de trailing en la propia clase de posición CPosition, que ya conocemos de artículos anteriores. En este caso, el funcionamiento del trailing-stop tendría el aspecto siguiente:

void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos)
  {
   pos.TrailingMode1();
  }

Sin embargo, esto solo trasladaría el problema de la clase CStrategy a la clase CPosition. En este caso, la propia clase CPosition comenzaría a contener una multitud potencial de algoritmos impropios de ella, aunque beneficiosos para el acompañamiento de las posiciones.  

Además, los propios algoritmos de trailing necesitan la definición de ciertos parámetros. Por ejemplo, para el trailing-stop clásico es necesario indicar la distancia en puntos entre el extremo de precio alcanzado y el nivel de stop-loss que se debe mantener. Por eso, aparte de los propios algoritmos, es imprescindible también guardar en algún lugar los parámetros para su funcionamiento. Si guardamos estos datos en las clases de infraestructura como CPosition y CStrategy, se mezclarán las variables de estas clases con multitud de variables de trailing, dificultando sustancialmente el funcionamiento de dichas clases.

 

Estandarización de las funciones de trailing. Clase CTrailing

Con frecuencia, la solución más efectiva es la más sencilla y comprobada. El trailing-stop no es una excepción. Si nos imaginamos el trailing stop como una clase especial que almacena parámetros para su trabajo, en particular, un algoritmo de desplazamiento de un trailing-stop concreto en forma de método, entonces todos los problemas anteriores con respecto a la ubicación de esta funcionalad desaparecerán. En realidad, si creamos el trailing-stop en forma de clase independiente, sus datos y métodos estarán entrelazados con los datos y métodos de la clase básica CStrategy y otros objetos de infraestructura del tipo CPosition.

Al desarrollar esta clase es necesario resolver dos cuestiones.

  1. La estructura interna de la clase del trailing-stop. La estandarización de su funcionamiento.
  2. La interacción de la clase proyectada con otros módulos del motor comercial CStrategy.

Vamos a analizar la primera de estas dos cuestiones. Resulta obvio que cada uno de los trailing-stops tiene su propio y único conjunto de parámetros. Por consiguiente, cualquier estandarización de este conjunto resulta imposible. Por otra parte, todos los algoritmos de trailing tienen un parámetro obligatorio, la propia posición cuyo stop-loss se debe modificar. La clase CPosition, que tan bien conocemos, se encargará de representar esta posición. Asimismo, cada tipo de trailing-stop debe tener un método cuya llamada produzca el cambio del stop-loss de la posición. Este método será un peculiar "botón" que pondrá en marcha el algoritmo de trailing, por eso tiene sentido convertir el nombre de este método en algo general para todos los tipos de trailing.

Hemos destacado las dos cuestiones esenciales para los dos tipos de trailing que se pueden transformar cómodamente en una clase básica especial, la llamaremos CTrailing. Esta contendrá los métodos de establecimiento de la posición actual, así como el método virtual Modify, con cuya ayuda se modificarán los stop-loss y el método virtual especial Copy, sobre el que hablaremos más tarde:

//+------------------------------------------------------------------+
//|                                                     Trailing.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Object.mqh>
#include "..\PositionMT5.mqh"
#include "..\Logs.mqh"
class CPosition;
//+------------------------------------------------------------------+
//| Clase básica del trailing-stop                                     |
//+------------------------------------------------------------------+
class CTrailing : public CObject
  {
protected:
   CPosition         *m_position;     // Posición cuyo stop-loss se debe modificar.
   CLog              *Log;
public:
                      CTrailing(void);
   void               SetPosition(CPosition *position);
   CPosition         *GetPosition(void);
   virtual bool       Modify(void);
   virtual CTrailing* Copy(void);
  };
//+------------------------------------------------------------------+
//| Constructor. Obtiene el módulo de registro                         |
//+------------------------------------------------------------------+
CTrailing::CTrailing(void)
  {
   Log=CLog::GetLog();
  }
//+------------------------------------------------------------------+
//| Método de modificación del trailing-stop que se debe            |
//| redefinir en la clase derivada de trailing                    |
//+------------------------------------------------------------------+
bool CTrailing::Modify(void)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Retorna una copia del ejemplar                                      |
//+------------------------------------------------------------------+  
CTrailing* CTrailing::Copy(void)
{
   return new CTrailing();
}
//+------------------------------------------------------------------+
//| Establece la posición cuyo stop-loss se debe modificar|
//+------------------------------------------------------------------+
void CTrailing::SetPosition(CPosition *position)
  {
   m_position=position;
  }
//+------------------------------------------------------------------+
//| Retorna la posición cuyo stop-loss se debe modificar   |
//+------------------------------------------------------------------+
CPosition *CTrailing::GetPosition(void)
  {
   return m_position;
  }
//+------------------------------------------------------------------+

Cualquier trailing-stop, sea como sea, se heredará de esta clase. La propia clase básica de trailing no contiene ningún parámetro de algoritmos concretos de desplazamiento de stop-loss. Por eso mismo, se alcanza la máxima flexibilidad al trabajar con ella. A pesar de que las funciones de trailing necesitan varios parámetros para su funcionamiento, el propio método Modify no acepta ninguno de ellos. Todos los parámetros se establecen directamente en las clases descendientes del trailing, con la ayuda de sus métodos especiales. De esta forma, en el momento de la llamada de la función Modify ya se conocerán todos los parámetros necesarios para el funcionamiento.

 

Interacción de la clase CTrailing con otros módulos de estrategias

Ahora solo tenemos la clase básica CTrailing, sin embargo, esto ya es suficiente para incluirla en la estructura general del motor comercial. Ubicamos la clase básica del trailing en la clase de la propia posición CPosition:

class CPosition
  {
public:
   CTrailing* Trailing;    // Módulo del trailing-stop
  };

Esta ubicación parece intuitivamente comprensible y natural. Y es que, en este caso, se puede controlar la posición como si fuese con la ayuda de la propia posición:

void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos)
  {
   pos.Trailing.Modify();
  }

Esto también es posible gracias al hecho de que cada trailing-stop tiene el método estandarizado Modify, es decir, siempre se sabe exactamente lo que hay que hacer para modificar la posición. 

Sin embargo, la integración del módulo de trailing-stop no termina aquí. En el ejemplo que hemos adjuntado más arriba se necesita el acompañamiento de la posición al nivel de la estrategia del usuario. Es necesario redefinir los métodos BuySupport y SellSupport y acompañar cada posición en la lógica del experto. Para simplificar aún más el acompañamiento de la posición, se puede incluir el módulo de trailing-stop directamente en la clase de la estrategia CStrategy:

class CStrategy
  {
public:
   CTrailing* Trailing;   // Módulo de trailing-stop para todas las posiciones
  };

Además, será necesario rellenar el método CallSupport, perteneciente a la clase CStrategy:

//+------------------------------------------------------------------+
//| Llama a la lógica de acompañamiento de posiciones, con la condición de que el estado  |
//| comercial no sea igual a TRADE_WAIT.                                   |
//+------------------------------------------------------------------+
void CStrategy::CallSupport(const MarketEvent &event)
  {
   m_trade_state=m_state.GetTradeState();
   if(m_trade_state == TRADE_WAIT)return;
   SpyEnvironment();
   for(int i=ActivePositions.Total()-1; i>=0; i--)
     {
      CPosition *pos=ActivePositions.At(i);
      if(pos.ExpertMagic()!=m_expert_magic)continue;
      if(pos.Symbol()!=ExpertSymbol())continue;
      if(CheckPointer(Trailing)!=POINTER_INVALID)
        {
         if(CheckPointer(Trailing)==POINTER_INVALID)
            pos.Trailing=Trailing.Copy();
         pos.Trailing.Modify();
         if(!pos.IsActive())
            continue;
        }
      if(pos.Direction()==POSITION_TYPE_BUY)
         SupportBuy(event,pos);
      else
         SupportSell(event,pos);
      if(m_trade_state==TRADE_STOP && pos.IsActive())
         ExitByStopRegim(pos);
     }
  }

La novedades se destacan en amarillo. Son muy sencillas: si se establece por defecto algún trailing-stop, y la posición actual no lo tiene, entonces también se establece en la posición actual, después de lo cual se modifica el stop-loss de la posición de acuerdo con la lógica del trailing. Sin embargo, hay una particularidad muy especial que se debe comprender y tener en cuenta. A cada nueva posición se le adjudica una copia del trailing-stop establecido por defecto, y no él mismo. Esto es necesario para evitar confusiones en la propia lógica del trailing-stop. Imagínese que el mismo ejemplar de trailing-stop va a controlar a la vez varias posiciones. Si dentro de su lógica calcula algunas variables para una posición y las recuerda, al darse la siguiente llamada, estas no serán válidas, puesto que la posición transmitida para el control ya será diferente. En este caso, surgirán errores muy extraños y difícilmente detectables. Para que esto no suceda, a cada psoción se le designa un ejemplar individual de trailing-stop, que no cambia durante toda la "vida" de la posición. Para designar un trailing-stop individual, es necesario realizar el procedimiento de copiado del trailing-stop por defecto. Puesto que CStrategy no sabe qué datos y variables internas se deben copiar, el procedimiento de copiado se delega en la clase final de trailing. Esta debe redefinir el método virtual Copy() de la clase básica CTrailing y retornar un enlace que haga referencia a la copia creada de sí mismo, en forma de clase genérica CTrailing. Adelantándonos un poco, pondremos un ejemplo del método de copiado para el trailing-stop clásico CTrailingClassic:

//+------------------------------------------------------------------+
//| Retorna una copia del ejemplar                                      |
//+------------------------------------------------------------------+  
CTrailing *CTrailingClassic::Copy(void)
  {
   CTrailingClassic *tral=new CTrailingClassic();
   tral.SetDiffExtremum(m_diff_extremum);
   tral.SetStepModify(m_step_modify);
   tral.SetPosition(m_position);
   return tral;
  }

El método crea un ejemplar del tipo CTrailingClassic, establece sus parámetros como iguales a los del ejemplar actual y después retorna el objeto en forma de puntero al tipo CTrailing.

El usuario tiene que recordar una simple regla cuando escriba la clase de su trailing: 

Para establecer un trailing-stop por defecto es necesario redefinir el método de copiado Copy de la clase básica CTrailing. En caso contrario, CStrategy no podrá acompañar automáticamente las posiciones abiertas. Si solo se planea usar el trailing-stop en los métodos BuySupport y SellSupport, entonces no será obligatorio redefinir el método virtual Copy.

La obligatoriedad de la redefinición del método Copy complica el planeamiento del trailing-stop de usario, pero hace la lógica del comportamiento de este módulo más segura, dificultando la aparición de errores relacionados con el procesamiento de los datos generales.

Ahora, tras introducir esta novedad, CStrategy podrá acompañar las posiciones de forma autónoma, usando el trailing-stop transmitido en calidad de lógica de acompañamiento. Si en el constructor de la estrategia de usuario se une el puntero CStrategy::Trailing con algún algoritmo de trailing-stop, entonces se convertirá en el trailing-stop  por defecto para todas las posiciones que vayan a pertenecer al experto actual.  Por consiguiente, para las estrategias que acompañen las posciones solo con trailing, no será en absoluto necesario redefinir los métodos BuySupport y SellSupport. El acompañamiento de posiciones se realiza de un modo completamente automático a nivel de CStrategy.

Preste atención a que en el código CallSupport, después de la llamada al método CTrailing::Modify se comprueba si la posición está activa o no. Esto significa que si en el proceso de modificación del trailing-stop se cierra la posición, no va a suceder nada terrible, el ciclo de iteración interrumpirá la llamada de los métodos redefinidos y continuará con la iteración de la siguiente posición. Gracias a esta particularidad, aparece una consecuencia interesante:

Como función de trailing-stop puede actuar prácticamente cualquier algoritmo de control de posiciones. No es necesario cambiar su stop-loss. El algoritmo de control de posiciones puede cerrarla en condiciones determinadas. Esta acción es de esperar, y CStrategy la procesará de modo convencional.


Uso práctico del trailing-stop. Ejemplo de implementación de un trailing-stop

Ahora que la clase básica ha sido redefinida y hemos enseñado al motor comercial de la estrategia a interactuar con ella, podemos crear implementaciones concretas de los trailing-stops. El primer candidato será el algoritmo clásico de trailing-stop. Funciona de una forma bastante simple. El trailing desplaza el stop-loss de la posición siguiendo los nuevos extremos alcanzados (para una posición larga) y los mínimos nuevos (para una posición corta). Si el precio es devuelto, el stop-loss permanece en el anterior nivel alcanzado. De esta forma, el stop-loss se desplaza siguiendo el precio a una determinada distancia de él. Esta distancia se establece con el parámetro correspondiente.

Nuestro trailing-stop también tendrá un segundo parámetro. Pero este no es obligatorio. Para que el cambio del stop-loss no sea demasiado frecuente, introduciremos una limitación adicional: el nivel del nuevo stop-loss deberá diferenciarse del anterior aunque sea en una cantidad de StepModify puntos. La magnitud de StepModify se indicará con un parámetro aparte. Este parámetro es importante para comerciar en FORTS. De acuerdo con el reglamento comercial en este mercado, la bolsa carga una tarifa adicional por las llamadas "transacciones ineficaces". Si el desplazamiento del stop-loss se repite con frecuencia y hay pocas operaciones, la bolsa le hará un cargo adicional al tráder. Por eso los algoritmos deberán tener en cuenta esta peculiaridad.

Y bien, mostremos el código de nuestro primer trailing-stop. Se basa en la clase CTrailing y redefine el método Modify:  

//+------------------------------------------------------------------+
//|                                              TrailingClassic.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include "Trailing.mqh"
//+------------------------------------------------------------------+
//| integramos los parámetros de trailing-stop directamente en la lista de parámetros   |
//| del experto                                                         |
//+------------------------------------------------------------------+
#ifdef SHOW_TRAILING_CLASSIC_PARAMS
input double PointsModify=0.00200;
input double StepModify=0.00005;
#endif
//+------------------------------------------------------------------+
//| Trailing-stop clásico                                       |
//+------------------------------------------------------------------+
class CTrailingClassic : public CTrailing
  {
private:
   double            m_diff_extremum;  // Distancia en puntos desde el extremo alcanzado hasta el stop-loss de la posición
   double            m_step_modify;    // Distancia mínima en puntos para cambiar el stop-loss
   double            FindExtremum(CPosition *pos);
public:
                     CTrailingClassic(void);
   void              SetDiffExtremum(double points);
   double            GetDiffExtremum(void);
   void              SetStepModify(double points_step);
   double            GetStepModify(void);
   virtual bool      Modify(void);
   virtual CTrailing *Copy(void);
  };
//+------------------------------------------------------------------+
//| Constructor. Inicializa los parámetros por defecto               |
//+------------------------------------------------------------------+
CTrailingClassic::CTrailingClassic(void) : m_diff_extremum(0.0),
                                           m_step_modify(0.0)
  {
#ifdef SHOW_TRAILING_CLASSIC_PARAMS
   m_diff_extremum=PointsModify;
   m_step_modify=StepModify;
#endif
  }
//+------------------------------------------------------------------+
//| Retorna una copia del ejemplar                                      |
//+------------------------------------------------------------------+  
CTrailing *CTrailingClassic::Copy(void)
  {
   CTrailingClassic *tral=new CTrailingClassic();
   tral.SetDiffExtremum(m_diff_extremum);
   tral.SetStepModify(m_step_modify);
   tral.SetPosition(m_position);
   return tral;
  }
//+------------------------------------------------------------------+
//| Establece la cantidad de puntos desde el extremo alcanzado      |
//+------------------------------------------------------------------+
void CTrailingClassic::SetDiffExtremum(double points)
  {
   m_diff_extremum=points;
  }
//+------------------------------------------------------------------+
//| Establece la magnitud de la modificación mínima en puntos         |
//+------------------------------------------------------------------+
void CTrailingClassic::SetStepModify(double points_step)
  {
   m_step_modify=points_step;
  }
//+------------------------------------------------------------------+
//| Establece la cantidad de puntos desde el extremo alcanzado         |
//+------------------------------------------------------------------+
double CTrailingClassic::GetDiffExtremum(void)
  {
   return m_diff_extremum;
  }
//+------------------------------------------------------------------+
//| Retorna la magnitud de la modificación mínima en puntos            |
//+------------------------------------------------------------------+
double CTrailingClassic::GetStepModify(void)
  {
   return m_step_modify;
  }
//+------------------------------------------------------------------+
//| Modifica el trailing-stop de acuerdo con la lógica del trailing         |
//| clásico                                                        |
//+------------------------------------------------------------------+
bool CTrailingClassic::Modify(void)
  {

   if(CheckPointer(m_position)==POINTER_INVALID)
     {
      string text="Invalid position for current trailing-stop. Set position with 'SetPosition' method";
      CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
      Log.AddMessage(msg);
      return false;
     }
   if(m_diff_extremum<=0.0)
     {
      string text="Set points trailing-stop with 'SetDiffExtremum' method";
      CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
      Log.AddMessage(msg);
      return false;
     }
   double extremum=FindExtremum(m_position);
   if(extremum == 0.0)return false;
   double n_sl = 0.0;
   if(m_position.Direction()==POSITION_TYPE_BUY)
      n_sl=extremum-m_diff_extremum;
   else
      n_sl=extremum+m_diff_extremum;
   if(n_sl!=m_position.StopLossValue())
      return m_position.StopLossValue(n_sl);
   return false;
  }
//+------------------------------------------------------------------+
//| Retorna los extremos de precio alcanzados durante el mantenimiento        |
//| de la posición. Para una posición larga se retornará el precio        |
//| máximo alcanzado. Para una corta, el precio mínimo  |
//| alcanzado.                                                      |
//+------------------------------------------------------------------+
double CTrailingClassic::FindExtremum(CPosition *pos)
  {
   double prices[];
   if(pos.Direction()==POSITION_TYPE_BUY)
     {
      if(CopyHigh(pos.Symbol(),PERIOD_M1,pos.TimeOpen(),TimeCurrent(),prices)>1)
         return prices[ArrayMaximum(prices)];
     }
   else
     {
      if(CopyLow(pos.Symbol(),PERIOD_M1,pos.TimeOpen(),TimeCurrent(),prices)>1)
         return prices[ArrayMinimum(prices)];
     }
   return 0.0;
  }
//+------------------------------------------------------------------+

El código principal de esta clase se encuentra en los métodos Modify y FindExtremum. El extremo o el mínimo del precio (dependiendo del tipo de posición) se busca en la historia con la ayuda del método FindExtremum. Gracias a esto, incluso después de reiniciar la estrategia o incluso durante su tiempo de inactividad, el nivel de stop-loss se calculará de forma apropiada.

Nuestra clase de trailing-stop contiene construcciones adicionales y por el momento poco comprensibles en forma de macros SHOW_TRAILING_CLASSIC_PARAMS y varias variables de entrada input. Hablaremos de estas funciones un poco más tarde, en el apartado especial "Conexión del trailing-stop a los ajustes del experto".

 

Conectando un trailing-stop a la estrategia CImpulse

En el artículo anterior "Experto comercial universal: trabajando con órdenes pendientes y cobertura" vimos por primera vez la estrategia CImpulse. Se trata de una táctica comercial basada en la entrada en los momentos en que se dan movimientos bruscos de precio. La estrategia presentada en el artículo acompañaba sus posiciones con la ayuda de una media móvil. La posición larga se cerraba cuando el precio de apertura de la barra quedaba por debajo de la media móvil. La corta se cerraba cuando el precio quedaba por encima de la media móvil. Vamos a mostrar de nuevo el código descrito en el artículo anterior, que implementa esta lógica:

//+------------------------------------------------------------------+
//| Acompañamiento de una posición larga con la media móvil Moving       |
//+------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      double target = Bid() - Bid()*(m_percent/100.0);
      if(target < Moving.OutValue(0))
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(Bid() < Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Acompañamiento de una posición corta con la media móvil Moving      |
//+------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      double target = Ask() + Ask()*(m_percent/100.0);
      if(target > Moving.OutValue(0))
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}

Ahora lo simplificaremos, sustituyendo las normas de acompañamiento de posición por la lógica del trailing-stop clásico. Sencillamente eliminaremos estos métodos del código de la estrategia, y añadiremos en su constructorr el trailing-stop clásico como trailing por defecto. La propia estrategia la renombraremos como CImpulseTrailingAuto:

//+------------------------------------------------------------------+
//| Inicialización de la estrategia, configuración del trailing-stop        |
//| durante el inicio                                                      |
//+------------------------------------------------------------------+
CImpulseTrailing::CImpulseTrailing(void)
{
   CTrailingClassic* classic = new CTrailingClassic();
   classic.SetDiffExtremum(0.00100);
   Trailing = classic;
}

Ahora, de acuerdo con la nueva lógica, la salida de la posición se realizará con el trailing-stop que se encuentre a una distancia de 0.00100 puntos del extremo alcanzado.

El código fuente completo de la estrategia CImpulse con trailing-stop automático se encuentra en el archivo  ImpulseTrailingAuto.mqh.

 

Conexión de los parámetros de trailing a los ajustes del experto

El método obtenido para trabajar con el trailing-stop es bastante cómodo. Pero los parámetros de trailing-stop se seguirán configurando dentro de la propia estrategia de usuario. Sería cómodo simplificar este procedimento: por ejemplo, mostrar de alguna forma los parámetros del trailing-stop en los ajustes del experto. El problema es que si este trailing-stop no se usa, los parámetros en los ajustes del experto seguirán existiendo de todas formas, lo que puede causar ambigüedad. Para que esto no suceda, podemos usar una compilación condicional. Añadimos al módulo del trailing-stop clásico los parámetros del propio trailing y el macro especial de la compilación condicional SHOW_TRAILING_CLASSIC_PARAMS:

//+------------------------------------------------------------------+
//|                                              TrailingClassic.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include "Trailing.mqh"
//+------------------------------------------------------------------+
//| integramos los parámetros de trailing-stop directamente en la lista de parámetros   |
//| del experto                                                         |
//+------------------------------------------------------------------+
#ifdef SHOW_TRAILING_CLASSIC_PARAMS
input double PointsModify = 0.00100;
input double StepModify =   0.00005;
#endif
//+------------------------------------------------------------------+
//| Trailing-stop clásico                                       |
//+------------------------------------------------------------------+
class CTrailingClassic : public CTrailing
  {
   ...
public:
                     CTrailingClassic(void);
   ...
  };
//+------------------------------------------------------------------+
//| Constructor. Inicializa los parámetros por defecto               |
//+------------------------------------------------------------------+
CTrailingClassic::CTrailingClassic(void) : m_diff_extremum(0.0),
                                           m_step_modify(0.0)
  {
   #ifdef SHOW_TRAILING_CLASSIC_PARAMS
   m_diff_extremum = PointsModify;
   m_step_modify = StepModify;
   #endif
  }

Ahora, si el macro  SHOW_TRAILING_CLASSIC_PARAMS ha sido defindo, en los ajustes del experto se integrarán los parámetros de trailing en el momento de la compilación:


Figura 1. Parámetros conectados dinámicamente PointsModify y StepModify.

Cuando el macro  SHOW_TRAILING_CLASSIC_PARAMS es comentado o no se encuentra disponible, los ajustes de trailing desaparecen de los parámetros del experto:


Fig 2. Desactivación de los parámetros de trailing-stop

Aparte de conectar los parámetros de trailing a los ajustes del experto, el macro  SHOW_TRAILING_CLASSIC_PARAMS configura la propia clase CTrailingClassic de tal forma que los parámetros de los ajustes sean añadidos a esta en el momento de su creación. De esta forma, cuando la estrategia la crea, ya contiene los parámetros introducidos por el propio usuario a través de la ventana de ajustes del experto. 

 

Trailing-Stop basado en una media móvil

En la parte anterior del artículo, CImpulse cerraba sus posiciones si el precio resultaba por debajo o encima de la media móvil. La media móvil se presentaba en forma de clase del indicador CIndMovingAverage. La clase CIndMovingAverage en sí misma se parece mucho a la clase de trailing. Se encarga de calcular los valores de la media móvil y permite configurar de forma flexible los propios parámetros de este indicador. Lo único que la diferencia del trailing-stop es la ausencia de un algoritmo propio de acompañamiento de posiciones En la clase CIndMovingAverage no hay método Modify(). Por otra parte, la clase básica CTrailing ya posee todos los métodos necesarios, pero en ella no hay algoritmos para trabajar con la media móvil. El método de composición permite unir las propiedades útiles de cada una de estas clases, creando sobre su base un nuevo tipo de trailing-stop: un trailing-stop basado en una media móvil. El funcionamiento del algoritmo será muy sencillo: colocará un stop-loss de la posición, igual al nivel de la media móvil. Asimismo, en el método Modify si introducirá una comprobación adicional: si por algún motivo el precio actual está por debajo (para la compra) o por encima (para la venta) del nivel calculado de stop-loss, entonces la posición se cerrará a los precios de mercado actuales. La clase al completo se muestra más abajo:

//+------------------------------------------------------------------+
//|                                               TrailingMoving.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include "Trailing.mqh"
#include "..\Indicators\MovingAverage.mqh"

//+------------------------------------------------------------------+
//| Trailing-stop basado en MovingAverage. Establece un stop-loss   |
//| de la posición, igual al nivel de la media móvil                         |
//+------------------------------------------------------------------+
class CTrailingMoving : public CTrailing
{
public:
   virtual bool       Modify(void);
   CIndMovingAverage* Moving;
   virtual CTrailing* Copy(void);
};
//+------------------------------------------------------------------+
//| Establece un stop-loss de la posición, igual al nivel de la media móvil |
//+------------------------------------------------------------------+
bool CTrailingMoving::Modify(void)
{
   if(CheckPointer(Moving) == POINTER_INVALID)
      return false;
   double value = Moving.OutValue(1);
   if(m_position.Direction() == POSITION_TYPE_BUY &&
      value > m_position.CurrentPrice())
      m_position.CloseAtMarket();
   else if(m_position.Direction() == POSITION_TYPE_SELL &&
      value < m_position.CurrentPrice())
      m_position.CloseAtMarket();
   else if(m_position.StopLossValue() != value)
      return m_position.StopLossValue(value);
   return false;
}
//+------------------------------------------------------------------+
//| Retorna una copia exacta del ejemplar CTrailingMoving               |
//+------------------------------------------------------------------+
CTrailing* CTrailingMoving::Copy(void)
{
   CTrailingMoving* mov = new CTrailingMoving();
   mov.Moving = Moving;
   return mov;
}

La función Modify compara el nivel de la media móvil con el nivel del stop actual, y si los niveles no coinciden, establece un nuevo nivel para la posición. El nivel de la media móvil se toma de la barra anterior ya formada, puesto que la barra actual siempre se encuentra en el estadio de formación. Asimismo, preste atención a que el propio indicador MovingAverage es declarado en forma de puntero. Esto se ha hecho especialmente para que el usuario pueda conectar a este trailing-stop cualquier objeto del tipo CIndMovingAverage. 

Ahora que la clase ya está escrita, comprobemos su funcionamiento en el simulador de estaregias. Aquí tenemos un pequeño vídeo que muestra su funcionamiento:

 

 

Traling-stop individual para una posición

Hemos analizado el mecanismo de trabajo con el trailing-stop. Gracias al método virtual Modify, el motor comercial CStrategy puede establecer de forma automática un trailing-stop para cada posición y llamar a su algoritmo de cálculo. Normalmente, con esto es suficiente, pero en ciertos casos es necesario acompañar cada posición individualmente. Así, para una posición es necesario establecer un tipo de trading, y para otra, otro tipo. Estas peculiaridades en el acompañamiento pueden ser unificadas y ejecutadas del lado del motor comercial, por eso el control de los trailing-stops se debe efectuar en la propia estrategia. Para esto, es suficiente con redefinir los métodos BuySupport y SellSupport, que tan bien conocemos. Aparte, en este caso ya no será necesario inicializar el trailing por defecto, ya que lo hicimos en el constructor de la estrategia de usuario.

Supongamos que las posiciones largas de la estrategia CImpulse deben ser acompañadas con la ayuda de un trailing-stop basado en una media móvil. Las posiciones cortas deben ser acompañadas con la ayuda de un trailing clásico. Ambos tipos de trailing ya han sido descritos más arriba. Redefinimos los métodos BuySupport y SellSupport convirtiéndolos en los siguientes:

//+------------------------------------------------------------------+
//| Acompañamiento de una posición larga con la media móvil Moving       |
//+------------------------------------------------------------------+
void CImpulseTrailing::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))
      return;
   if(pos.Trailing == NULL)
   {
      CTrailingMoving* trailing = new CTrailingMoving();
      trailing.Moving = GetPointer(this.Moving);
      pos.Trailing = trailing;
   }
   pos.Trailing.Modify();
}
//+------------------------------------------------------------------+
//| Acompañamiento de una posición corta con la media móvil Moving      |
//+------------------------------------------------------------------+
void CImpulseTrailing::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))
      return;
   if(pos.Trailing == NULL)
   {
      CTrailingClassic* trailing = new CTrailingClassic();
      trailing.SetDiffExtremum(0.00100);
      pos.Trailing = trailing;
   } 
   pos.Trailing.Modify();
}

Preste atención: en el caso con el trailing con media móvil, como parámetro se establece una clase completa CIndMovingAverage, de la que ya disponemos en la estrategia en calidad de objeto Moving. Con la ayuda de una línea de código le hemos indicado al trailing qué objeto usar como cálculo del nivel de stop-loss. 

En el método SupportSell, en las nuevas posiciones se establece otro tipo de trailing para el que se configura su propio conjunto de parámetros. En este caso, se establece un canal de seguimiento de 0.00100 puntos desde el extremo alcanzado por el precio. 

El código completo de la estrategia CImpulse con trailings individuales para cada posición se muestra en el archivo ImpulseTrailingManual.mqh.

 

Lista breve de correcciones en la última versión

El motor comercial CStrategy ha sufrido cambios significativos desde el momento de la publicación de la primera parte del artículo. Se le han ido añadiendo una nueva funcionalidad y modúlos que han ampliado las posibilidades comerciales. Asimismo, desde que se publicó, se han lanzado varias versiones del compilador con diferentes cambios. Algunos de ellos son ahora incompatibles con la versiones antiguas del proyecto, por eso hemos tenido que corregir también este. Mientras se realizaban estas correcciones y expansiones, ha resultado inevitable la aparición de errores. Vamos a mostrar en este apartado la corrección de aquellos errores que han sido detectados hasta el momento de la publicación de la versión actual del motor.

  • En el proyecto se ha incluido un panel de estrategia comercial. Debido a un error en el compilador de la anterior versión, en la 3 y 4 parte del artículo el panel comercial estaba desactivado, debido a problemas de compatibilidad con el compilador. Después de corregir el compilador, volvió a aparecer en la quinta parte del artículo, pero no funcionaba como debería. En la sexta edición del motor comercial la capacidad del panel ha sido completamente restablecida.
  • En el panel comercial se había producido un error: en lugar de representar el modo "SellOnly", se representaba dos veces el modo "BuyOnly". Este error ha sido corregido.
  • En las anteriores versiones, al cambiar el modo con la ayuda del panel, el modo real de la estrategia no cambiaba. Este error se he corregido en la sexta versión.
  • Ahora, al modificar el modo comercial se ha añadido un nuevo comportamiento: con el modo SellOnly se cierran automáticamente no solo las posiciones de compra, sino también todas las órdenes pendientes de compra que pertenezcan a la estrategia actual. Lo mismo se aplica a BuyOnly: todas las órdenes pendientes de venta en este modo también se cancelan. Al elegir el modo "Stop", todas las órdene pendientes, sin importar cuál sea su dirección, también son eliminadas.

Pido a los lectores que, en caso de detectar nuevos bugs o errores, me los comuniquen. Los errores que se vayan encontrando, serán corregidos. 


Conclusión

En la sexta parte del artículo se ha introducido una nueva funcionalidad en el motor comercial CStrategy. Ahora da soporte a trailing-stops. Cada trailing-stop es una clase especial que contiene el método unificado Modify, que cambia el nivel de stop-loss. Asimismo, contiene datos y parámetros especiales que configuran el propio algoritmo de desplazamiento del trailing-stop. Hay dos formas de trabajar con un trailing-stop.

  • Delegar el trabajo con el trailing-stop en el motor comercial de la estrategia o modo Piloto automático. En este caso, cada posición será acompañada automáticamente por defecto con un trailing. En lo que respecta al control, no se requerirá de acción alguna por parte de la estrategia de usuario. 
  • Control del trailing-stop a nivel de la estrategia de usuario. En este caso, la propia estrategia de usuario controla sus posiciones con la ayuda del trailing. Este modo se debe usar para implementar tácticas de control complejas, como por ejemplo cuando se utilizan diferentes algoritmos de trailing para diferentes posiciones.

Cada trailing-stop contiene información completa sobre la posición que debe acompañar. Asimismo, el método de modificación puede cerrarla en cualquier momento. Gracias a ello, los algoritmos de trailing alcanzan una gran flexibilidad. De manera formal, cualquier algoritmo de control de posiciones puede ser un algoritmo de trailing-stop. Por ejemplo, en lugar de cambiar el nivel de stop-loss, el algoritmo puede cambiar el nivel de take-profit. Estos cambios no perturbarán la lógica, y el motor comercial funcionará correctamente.