
Cómo crear cualquier tipo de Trailing Stop y conectarlo a un asesor experto
Contenido
- Introducción
- Clase básica final
- Clases de trailing según el indicador
- Clase de trailing según los valores de nivel Stop Loss especificados
- Conectamos un trailing stop a un asesor experto
- Conclusión
Introducción
Continuando con el tema del trailing stop iniciado en el último artículo, hoy analizaremos las clases de trailing necesarias para crear cómodamente varios algoritmos de trailing de posiciones Stop Loss. Basándonos en las clases creadas, podremos implementar cualquier algoritmo de desplazamiento de stops: según la separación del stop respecto al precio actual, según los indicadores, según los valores especificados de los niveles Stop Loss, etc. Después de leer este artículo, seremos capaces de crear y conectar cualquier algoritmo de desplazamiento de stop de la posición a cualquier EA. Al mismo tiempo, la conexión y el uso del propio trailing serán cómodos y fáciles de entender.
Vamos a considerar brevemente el algoritmo de funcionamiento del trailing stop. Nos atendremos a la condición de que para cada trailing se puedan utilizar tres condiciones para su funcionamiento:
- trailing start - número de puntos de beneficio de la posición en el que se activará el trailing stop;
- trailing step - número de pips que el precio deberá pasar en la dirección del beneficio de la posición para el siguiente desplazamiento de la posición Stop Loss;
- distancia de trailing - distancia desde el precio actual a la que se mantendrá el Stop Loss.
Estos tres parámetros pueden aplicarse a cualquier trailing. Cualquiera de estos parámetros puede o no estar presente en los ajustes de trailing en caso de que no sean necesarios o sean sustituidos por algún valor en el algoritmo de trailing. Un ejemplo de sustitución del parámetro "distancia de trailing" sería el valor del indicador al que se fija el Stop Loss de la posición. En este caso, si este parámetro está activado, el stop se fijará no en el precio establecido por el indicador, sino a una distancia en pips del precio indicado.
En general, los tres parámetros anteriores son los más usados en los distintos trailings, y los tendremos en cuenta a la hora de crear las clases de trailing.
Al desplazar una posición Stop Loss al precio requerido, deberemos realizar comprobaciones:
- El precio StopLoss no deberá estar más cerca del precio actual que el valor de StopLevel del símbolo (SYMBOL_TRADE_STOPS_LEVEL, el margen mínimo en pips desde el precio de cierre actual para establecer órdenes Stop);
- El precio StopLoss no deberá ser igual al precio ya fijado y deberá encontrarse por encima del nivel actual de Stop Loss para una posición larga y por debajo para una posición corta;
- si los parámetros anteriores se utilizan en el algoritmo de trailing, las condiciones establecidas por estos parámetros deberán comprobarse adicionalmente.
Son comprobaciones básicas. Cualquier algoritmo de trailing funciona de la misma forma: se introduce el precio necesario para fijar un stop, se comprueban todas las restricciones necesarias y, si se superan todas las comprobaciones, el stop de la posición se desplazará al nivel especificado.
Así es como puede funcionar un trailing simple:
- en el parámetro "trailing start" se especifica el beneficio en pips al alcanzar el cual será necesario iniciar el trailing, o cero, si no se utiliza este parámetro;
- en el parámetro "trailing step" se especifica el número de puntos de beneficio después de los cuales el stop deberá situarse detrás del precio, o cero si no se usa este parámetro;
- en el parámetro "trailing distance" se establece la distancia desde el precio actual hasta el stop de la posición en la que deberá mantenerse el stop, o cero si no se utiliza este parámetro;
- adicionalmente podemos establecer el símbolo y el número mágico de las posiciones a monitorear, o bien NULL y -1 respectivamente, si deseamos rastrear las posiciones de todos los símbolos y con cualquier número mágico.
Cuando el beneficio de una posición alcanza el número indicado de pips, se activará el trailing y se establecerá una posición de stop a la distancia especificada del precio actual. A continuación, después de que el precio haya recorrido el número especificado de puntos hacia el lado del beneficio de la posición (paso de trailing), la posición de stop se desplazará nuevamente detrás del precio para mantener la distancia especificada con respecto a él. Y así continuamente hasta que el precio vaya en contra de la posición. En este caso, el stop se mantendrá en el nivel ya fijado. Una vez que el precio lo alcance, la posición se cerrará con beneficios mediante un Stop Loss. Por lo tanto, el trailing stop nos permitirá mantener el beneficio gracias al cierre de la posición cuando el precio se invierta, dejando al mismo tiempo que el precio "vague" dentro del rango establecido para el stop del precio, y tirando paso a paso hacia arriba de la parada después del precio, si se mueve en la dirección de la posición.
En lugar del parámetro "trailing distance", podemos especificar un valor de precio obtenido, por ejemplo, de un indicador. En este caso obtendremos un trailing en el indicador. Podemos transmitir el valor de una de las velas anteriores (High, Low, por ejemplo), entonces lograremos una búsqueda según los precios de la barra, etc. Pero el trailing básico, su algoritmo, permanecerá inalterado.
Por ello, para crear cualquier algoritmo de trailing stop, primero deberemos crear un trailing simple que nos permita transmitir los precios deseados para las posiciones de Stop Loss, y también desplace las posiciones de Stop Loss a ese nivel con todas las comprobaciones necesarias.
En el último artículo ya vimos las funciones de inclusión en las que había un trailing simple y trailings sobre distintos indicadores. Este enfoque permite conectar un trailing stop al EA para rastrear las posiciones del símbolo actual. Pero tiene algunas limitaciones: para cada trailing del indicador, deberemos crear un indicador en el propio EA y enviar su manejador a la función de trailing. Las propias funciones de trailing solo podrán trabajar con las posiciones del símbolo actual.
Si utilizamos clases de trailing, podremos crear muchas instancias de una clase de trailing con diferentes configuraciones, y luego todos los trailings creados de esta manera podrán trabajar simultáneamente en un asesor experto según un determinado algoritmo establecido por el programador. Es decir, para cada conjunto de parámetros prácticamente "con un par de líneas de código" creará su propio trailing que funcionará según su propio algoritmo. Cada uno de los trails creados podrá ejecutarse en el asesor experto de acuerdo con ciertas condiciones, creando complejos algoritmos para los trailing de posiciones de Stop Loss.
Incluso si crean dos trailings idénticos, pero para símbolos diferentes, se creará un trailing distinto para cada símbolo, que trabajará sobre los datos del símbolo que se ha especificado al crear el trailing. Esto simplificará enormemente el uso de las trailings en nuestros programas: solo tendremos que crear un trailing con los parámetros necesarios y llamarlo en los manejadores del EA. Para ser justos, debemos señalar que este enfoque podría no resultar óptimo en algunos algoritmos, ya que para cada tipo de trailing se ejecutará una búsqueda diferente de las posiciones existentes en el terminal. Si creamos un algoritmo realmente universal que use la misma lista de posiciones terminales para cada trailing, podría resultar que el coste de su diseño y desarrollo no sea comparable a su demanda. Y además, esto queda fuera del alcance de este artículo.
Por lo tanto, primero tendremos que escribir un marco: una simple clase de trailing stop que desplace el Stop Loss de las posiciones a un precio especificado con todas las comprobaciones necesarias.
En el artículo de la semana pasada, este simple trailing básico se llamaba TrailingStopByValue(). Aquí lo llamaremos SimpleTrailing, y podremos utilizarlo plenamente en nuestros programas.
Al igual que en el artículo anterior, todas las clases de monitoreo se ubicarán en un archivo. Este archivo se introducirá en el EA y se utilizará. Normalmente los archivos de inclusión se almacenan en la carpeta \MQL5\Include\ o en una subcarpeta de este directorio.
Clase básica final
En la carpeta del terminal \MQL5\Include\, crearemos una nueva subcarpeta Trailings\ y en ella un nuevo archivo de clase con el nombre Trailings.mqh:
El nombre de la clase deberá ser CSimpleTrailing, el nombre del archivo creado, Trailings.mqh, y la clase básica deberá ser una clase de objeto básico CObject de la biblioteca estándar:
Al finalizar el wizard MQL, obtendremos esta plantilla de clase en la carpeta \MQL5\Include\Trailings\Trailings.mqh:
//+------------------------------------------------------------------+ //| Trailings.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" class CSimpleTrailing : public CObject { private: public: CSimpleTrailing(); ~CSimpleTrailing(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::~CSimpleTrailing() { } //+------------------------------------------------------------------+
Luego conectaremos el archivo de objetos básicos de la biblioteca estándar al archivo creado:
//+------------------------------------------------------------------+ //| Trailings.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Object.mqh> //+------------------------------------------------------------------+ //| Class of the position StopLoss simple trailing | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject
¿Por qué debemos heredar todas las clases de trailing creadas del objeto básico de la biblioteca estándar?
Esto nos permitirá crear fácilmente colecciones de trailings a partir de estas clases: ponerlas en listas, buscar en las listas los objetos deseados, etc. Es decir, si solo queremos usar futuros objetos de clase como instancias en archivos del EA, no será necesario heredar de CObject. Pero si queremos convertir estas clases en colecciones completas y utilizar todas las características de la Biblioteca Estándar, entonces estas clases deberán ser heredadas de la clase básica CObject:
La clase CObject es la clase básica para construir la biblioteca estándar MQL5 y proporciona a todos sus descendientes la posibilidad de ser un elemento de una lista vinculada.
Además, se definirán una serie de métodos virtuales para su posterior implementación en clases descendientes.
En la sección privada de la clase, declararemos tres métodos:
class CSimpleTrailing : public CObject { private: //--- check the criteria for modifying the StopLoss position and return the flag bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick); //--- modify StopLoss of a position by its ticket bool ModifySL(const ulong ticket, const double stop_loss); //--- return StopLevel in points int StopLevel(void); protected:
El método CheckCriterion() retornará una bandera que indicará si se han superado con éxito (o no) todas las comprobaciones necesarias para modificar la posición StopLoss.
El método ModifySL() modificará el Stop Loss de la posición según el valor especificado.
El método StopLevel() retornará el valor de la propiedad StopLevel del símbolo.
Todos estos métodos eran funciones separadas en el último artículo, pero ahora funcionarán como parte de la clase básica de trailing, de la que heredaremos otras clases de otras clases de trailing.
En la sección protegida de la clase, declararemos todas las variables necesarias para que la clase funcione:
protected: string m_symbol; // trading symbol long m_magic; // EA ID double m_point; // Symbol Point int m_digits; // Symbol digits int m_offset; // stop distance from price int m_trail_start; // profit in points for launching trailing uint m_trail_step; // trailing step uint m_spread_mlt; // spread multiplier for returning StopLevel value bool m_active; // trailing activity flag //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public:
De todas las variables declaradas aquí, solo el "multiplicador de spread" requiere una explicación. El valor StopLevel del símbolo será la distancia al precio actual, más cerca de la cual no se podrá colocar un Stop Loss. Y, si el valor de StopLevel está a cero en el servidor, no significará que no haya StopLevel. Esto significará que el nivel de stop es flotante y normalmente será igual a dos spreads. A veces tres. Todo dependerá de la configuración del servidor. Por lo tanto, aquí podremos fijar este multiplicador de forma independiente. Si utilizamos un spread doble para el valor StopLevel, el valor de la variable m_spread_mlt se establecerá en dos (valor predeterminado). Si es triple, será tres, etc.
El método virtual GetStopLossValue() calculará y retornará el precio StopLoss de una posición. En los distintos tipos de trailing, el nivel de Stop Loss en el que colocar una posición de stop se calculará de forma diferente. Precisamente por esto dicho método se declarará virtual, y deberá ser redefinido en cada clase de trailing heredada si tienen un cálculo de Stop Loss diferente al que se escribirá en este método de esa clase.
En la sección pública de la clase escribiremos métodos para establecer y retornar los valores a las variables declaradas en la sección protegida de la clase, así como constructores y destructores de la clase:
public: //--- set trailing parameters void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); } void SetMagicNumber(const long magic) { this.m_magic = magic; } void SetStopLossOffset(const int offset) { this.m_offset = offset; } void SetTrailingStart(const int start) { this.m_trail_start = start; } void SetTrailingStep(const uint step) { this.m_trail_step = step; } void SetSpreadMultiplier(const uint value) { this.m_spread_mlt = value; } void SetActive(const bool flag) { this.m_active = flag; } //--- return trailing parameters string Symbol(void) const { return this.m_symbol; } long MagicNumber(void) const { return this.m_magic; } int StopLossOffset(void) const { return this.m_offset; } int TrailingStart(void) const { return this.m_trail_start; } uint TrailingStep(void) const { return this.m_trail_step; } uint SpreadMultiplier(void) const { return this.m_spread_mlt; } bool IsActive(void) const { return this.m_active; } //--- launch trailing with StopLoss offset from the price bool Run(void); //--- constructors CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()), m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {} CSimpleTrailing(const string symbol, const long magic, const int trailing_start, const uint trailing_step, const int offset); //--- destructor ~CSimpleTrailing() {} };
La clase tendrá dos constructores:
- El primer constructor será el predeterminado, donde el símbolo actual se utilizará como símbolo; el número mágico se establecerá como -1 (para todas las posiciones del símbolo actual sin excepción), mientras que los parámetros de trailing se establecerán en cero.
- El segundo constructor será paramétrico; en sus parámetros formales, se transmitirán al constructor el nombre del símbolo, el valor del número mágico de la posición y tres parámetros de trailing: inicio, paso y separación.
Vamos a analizar la implementación del constructor paramétrico:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic, const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2) { this.SetSymbol(symbol); this.m_magic = magic; this.m_trail_start= trail_start; this.m_trail_step = trail_step; this.m_offset = offset; }
A diferencia del constructor por defecto, en el que, en la línea de inicialización se le asignarán a todas las variables los valores del símbolo actual y valores nulos de los parámetros de trailing, aquí transmitiremos al constructor los valores que precisamente se establecerán en las variables correspondientes. El método SetSymbol() asignará valores a varias variables a la vez:
void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); }
En ambos constructores el multiplicador de spread se fijará en 2. De ser necesario, podremos cambiarlo utilizando el método SetSpreadMultiplier().
Método virtual que calcula y retorna el nivel StopLoss de la posición seleccionada:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(tick.bid - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(tick.ask + this.m_offset * this.m_point); default : return 0; } }
Como se trata de un trailing simple, aquí la posición de Stop Loss se mantendrá siempre a una distancia determinada del precio actual.
Los valores calculados de las posiciones StopLoss se retornarán desde el método y se comprobarán posteriormente en el método CheckCriterion():
//+------------------------------------------------------------------+ //|Check the StopLoss modification criteria and return a flag | //+------------------------------------------------------------------+ bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick) { //--- if the stop position and the stop level for modification are equal or a zero StopLoss is passed, return 'false' if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0) return false; //--- trailing variables double trailing_step = this.m_trail_step * this.m_point; // convert the trailing step into price double stop_level = this.StopLevel() * this.m_point; // convert the symbol StopLevel into price int pos_profit_pt = 0; // position profit in points //--- depending on the type of position, check the conditions for modifying StopLoss switch(pos_type) { //--- long position case POSITION_TYPE_BUY : pos_profit_pt = int((tick.bid - pos_open) / this.m_point); // calculate the position profit in points if(tick.bid - stop_level > value_sl // if the StopLoss level is lower than the price with the StopLevel level set down from it (the distance according to StopLevel is maintained) && pos_sl + trailing_step < value_sl // if the StopLoss level exceeds the trailing step set upwards from the current position StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true' ) return true; break; //--- short position case POSITION_TYPE_SELL : pos_profit_pt = int((pos_open - tick.ask) / this.m_point); // calculate the position profit in points if(tick.ask + stop_level < value_sl // if the StopLoss level is higher than the price with the StopLevel level set upwards from it (the distance according to StopLevel is maintained) && (pos_sl - trailing_step > value_sl || pos_sl == 0) // if the StopLoss level is below the trailing step set downwards from the current StopLoss or a position has no StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true' ) return true; break; //--- return 'false' by default default: break; } //--- conditions are not met - return 'false' return false; }
Si el valor StopLoss transmitido al método cumple todos los criterios de todos los filtros, el método retornará true y la posición de Stop Loss se modificará utilizando el método ModifySL():
//+------------------------------------------------------------------+ //| Modify StopLoss of a position by ticket | //+------------------------------------------------------------------+ bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss) { //--- if the EA stop flag is set, report this in the journal and return 'false' if(::IsStopped()) { Print("The Expert Advisor is stopped, trading is disabled"); return false; } //--- if failed to select a position by ticket, report this in the journal and return 'false' ::ResetLastError(); if(!::PositionSelectByTicket(ticket)) { ::PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, ::GetLastError()); return false; } //--- declare the structures of the trade request and the request result MqlTradeRequest request= {}; MqlTradeResult result = {}; //--- fill in the request structure request.action = TRADE_ACTION_SLTP; request.symbol = ::PositionGetString(POSITION_SYMBOL); request.magic = ::PositionGetInteger(POSITION_MAGIC); request.tp = ::PositionGetDouble(POSITION_TP); request.position = ticket; request.sl = ::NormalizeDouble(stop_loss, this.m_digits); //--- if the trade operation could not be sent, report this to the journal and return 'false' if(!::OrderSend(request, result)) { PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, ::GetLastError()); return false; } //--- request to change StopLoss position successfully sent return true; }
Para obtener el nivel de stop, utilizaremos el método StopLevel():
//+------------------------------------------------------------------+ //| Return StopLevel in points | //+------------------------------------------------------------------+ int CSimpleTrailing::StopLevel(void) { int spread = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD); int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL); return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level); }
Como el nivel de stop no es un valor constante y puede cambiar dinámicamente, cada vez que llamamos al método, solicitaremos los valores necesarios a las propiedades del símbolo. Si el StopLevel del símbolo es igual a cero, utilizaremos el valor de spread del símbolo multiplicado por 2. Este valor se establecerá por defecto.
Ya creamos los tres métodos anteriores en el último artículo como funciones independientes. Ahora estos métodos estarán ocultos en una sección privada de la clase y realizarán sus funciones sin acceso a ellas desde el exterior.
El método principal de trailing será el método Run(), que iniciará el ciclo de selección de posiciones y desplazamiento de sus stops a los valores calculados:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- if disabled, leave if(!this.m_active) return false; //--- trailing variables MqlTick tick = {}; // price structure bool res = true; // result of modification of all positions //--- check the correctness of the data by symbol if(this.m_point==0) { //--- let's try to get the data again ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- in a loop by the total number of open positions int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- if failed to get the prices, move on if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); } //--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter return res; }
El método buscará en la lista de posiciones activas del terminal, las filtrará según el símbolo y el número mágico y desplazará los stops de la posición a la distancia calculada del precio actual. El resultado de la modificación del StopLoss de cada posición se añadirá a la variable res. Al final del ciclo de modificación de los stops de todas las posiciones, la variable contendrá una bandera true solo si los stops de todas las posiciones que coinciden con el filtro símbolo/número mágico han sido modificados con éxito. O bien, si al menos una posición no ha sido modificada con éxito, la bandera contendrá el valor false. Esto se hará para controlar de forma adicional la modificación del stop desde el exterior.
Ahora vamos a conectar esta clase al asesor experto para probar su rendimiento.
Para la prueba, tomaremos el asesor experto Moving Average.mq5 de la carpeta \MQL5\Experts\Examples\ del terminal y lo guardaremos con el nombre MovingAverageWithSimpleTrail.mq5.
Luego conectaremos el archivo de la clase de trailing al asesor experto, escribiremos los parámetros adicionales en la configuración y crearemos una instancia de la clase de trailing simple:
//+------------------------------------------------------------------+ //| MovingAverageWithSimpleTrail.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh> #include <Trailings\Trailings.mqh> input group " - Moving Averages Expert Parameters -" input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift input group " - Simple Trailing Parameters -" input int InpTrailingStart = 10; // Trailing start input int InpTrailingStep = 20; // Trailing step in points input int InpTrailingOffset = 30; // Trailing offset in points input bool InpUseTrailing = true; // Use Trailing Stop //--- int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; CSimpleTrailing ExtTrailing; // simple trailing class instance #define MA_MAGIC 1234501 //+------------------------------------------------------------------+
En el manejador OnInit(), estableceremos los parámetros de trailing:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- prepare trade class to control positions if hedging mode is active ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(Symbol()); //--- Moving Average indicator ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); } //--- set trailing parameters ExtTrailing.SetActive(InpUseTrailing); ExtTrailing.SetSymbol(Symbol()); ExtTrailing.SetMagicNumber(MA_MAGIC); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); //--- ok return(INIT_SUCCEEDED); }
En el manejador OnTick(), empezaremos con el trailing:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- launch trailing ExtTrailing.Run(); }
Eso es todo lo que necesitaremos poner adicionalmente en el archivo del EA. Luego los compilaremos y ejecutaremos en el simulador de estrategias en el gráfico EURUSD, M15; una sola prueba durante el último año. Todos los ticks sin retrasos de ejecución.
Después estableceremos los parámetros como sigue (el trailing estará desactivado):
Realizaremos la prueba con el trailing desactivado y veremos el resultado:
Ahora pasaremos al uso del trailing:
y haremos exactamente la misma prueba.
El resultado de la prueba ha sido el siguiente:
El resultado ha resultado un poco mejor. Podemos ver que el trailing mejora un poco las estadísticas con estos ajustes.
Ahora, basándonos en el trailing simple que hemos creado, podremos crear cualquier otro trailing.
Así, crearemos varias clases de trailing según los indicadores, según el indicador Parabolic SAR y según distintas medias móviles de la lista de indicadores de tendencia presentada en el terminal de cliente.
Clases de trailing según el indicador
Ahora continuaremos escribiendo código en el mismo archivo. Pero antes de empezar una nueva clase de trailing según el indicador, deberemos recordar que para los trailings según los valores del indicador, necesitaremos crear el propio indicador de cuyos datos obtendremos los valores de los niveles de Stop Loss. En el caso de los indicadores, para crearlos deberemos especificar el símbolo y el marco temporal sobre cuyos datos el indicador construirá sus series de datos. Y también necesitaremos guardar el manejador del indicador creado en una variable para que podamos utilizar este manejador para llamar al indicador y recuperar los datos. La obtención de datos según el manejador no dependerá del tipo de indicador, los datos se obtendrán según la función CopyBuffer() con la indicación del indicador según el manejador.
Todas las acciones para obtener datos del indicador deberán detallarse en las clases de trailing según los indicadores. Pero en este caso todas estas acciones deberán prescribirse en cada clase de cada trailing, trabajando según indicadores diferentes. Es posible, pero no lo mejor. Será mejor crear una clase básica de trailing según los indicadores, que implementará los métodos para obtener los datos del indicador según el manejador y prescribirá algunas variables que serán las mismas para cada indicador (símbolo, marco temporal, índice de serie temporal de los datos y manejador del indicador). Y las propias clases de trailing heredarán de esta clase básica. Entonces, la estructura de la clase estará clasificada: la clase básica organizará el acceso a los datos del indicador según el manejador, y la clase heredada creará el indicador necesario y trabajará con sus datos.
Vamos a seguir escribiendo el código en el archivo Trailings.mqh. Escribiremos una clase básica de trailing según los indicadores:
//+------------------------------------------------------------------+ //| Base class of indicator-based trailings | //+------------------------------------------------------------------+ class CTrailingByInd : public CSimpleTrailing { protected: ENUM_TIMEFRAMES m_timeframe; // indicator timeframe int m_handle; // indicator handle uint m_data_index; // indicator data bar string m_timeframe_descr; // timeframe description //--- return indicator data double GetDataInd(void) const { return this.GetDataInd(this.m_data_index); } //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- set parameters void SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7); } void SetDataIndex(const uint index) { this.m_data_index=index; } //--- return parameters ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } uint DataIndex(void) const { return this.m_data_index; } string TimeframeDescription(void) const { return this.m_timeframe_descr; } //--- return indicator data from the specified timeseries index double GetDataInd(const int index) const; //--- constructors CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {} CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(1), m_handle(INVALID_HANDLE) { this.SetTimeframe(timeframe); } //--- destructor ~CTrailingByInd(void) { if(this.m_handle!=INVALID_HANDLE) { ::IndicatorRelease(this.m_handle); this.m_handle=INVALID_HANDLE; } } };
La clase se heredará de la clase de trailing simple, por lo que tendrá toda la funcionalidad de dicha clase, además se implementarán los métodos necesarios para acceder a los datos del indicador según el manejador.
La sección protegida de la clase contendrá las variables de clase, un método para recibir los datos del indicador según el manejador y un método virtual para calcular los niveles de Stop Loss que redefinirá el método homónimo de la clase padre.
La sección pública de la clase contendrá los métodos para establecer y obtener las propiedades del indicador y los constructores con el destructor.
El constructor por defecto, en su línea de inicialización, transmitirá a la clase padre el símbolo actual, un número mágico con valor -1 (todas las posiciones) y parámetros cero para el trailing. El periodo actual del gráfico se establecerá como el marco temporal del gráfico para calcular el indicador.
En el constructor paramétrico, el símbolo y el periodo del gráfico para calcular el indicador, así como todos los parámetros de trailing se transmitirán en los parámetros formales del constructor.
En el destructor de la clase, la parte calculada del indicador se liberará y la variable que almacena el manejador se establecerá en INVALID_HANDLE.
Método que retorna los datos del indicador con el índice especificado de la serie temporal:
//+------------------------------------------------------------------+ //| Return indicator data from the specified timeseries index | //+------------------------------------------------------------------+ double CTrailingByInd::GetDataInd(const int index) const { //--- if the handle is invalid, report this and return "empty value" if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__); return EMPTY_VALUE; } //--- get the value of the indicator buffer by the specified index double array[1]; ::ResetLastError(); if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1) { ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError()); return EMPTY_VALUE; } //--- return the received value return array[0]; }
Usando CopyBuffer(), obtendremos el valor de una barra del indicador según el índice especificado y lo retornaremos si se ha obtenido con éxito. Si se produce un error, enviaremos un mensaje al registro y retornaremos un valor vacío (EMPTY_VALUE).
La función CopyBuffer() trabajará con los datos de cualquier indicador según su manejador. Por consiguiente, este método será universal y se usará sin cambios en todas las clases de trailing según los indicadores.
Método virtual que calcula y retorna el nivel StopLoss de la posición seleccionada:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- get the indicator value as a level for StopLoss double data=this.GetDataInd(); //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid); case POSITION_TYPE_SELL : return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask); default : return 0; } }
Este método, en la clase padre, simplemente realizaremos el cálculo de la separación del nivel de Stop Loss a partir del precio. En la misma clase en este método, deberemos obtener los datos del indicador y transmitirlos como un nivel Stop Loss. Si no ha recibido los datos del indicador, el método devolverá el precio actual, lo que no permitirá colocar una posición Stop Loss, ya que el StopLevel no permitirá colocar un stop al precio actual.
Como resultado, esta clase implementará el acceso a los datos del indicador según su manejador y el cálculo del nivel para la posición StopLoss según el valor del indicador, y todos sus métodos estarán disponibles en las clases hijas.
Ahora puede crear clases de trailing según los indicadores que estén basadas en la clase creada.
Clase de trailing según Parabolic SAR
Vamos a seguir escribiendo el código en el mismo archivo \MQL5\Include\Trailings\Trailings.mqh.
La clase de trailing según Parabolic SAR, al igual que las otras clases de trailings según los indicadores, deberá heredarse de la clase básica de trailings según los indicadores:
//+------------------------------------------------------------------+ //| Parabolic SAR position StopLoss trailing class | //+------------------------------------------------------------------+ class CTrailingBySAR : public CTrailingByInd { private: double m_sar_step; // Parabolic SAR Step parameter double m_sar_max; // Parabolic SAR Maximum parameter public: //--- set Parabolic SAR parameters void SetSARStep(const double step) { this.m_sar_step=(step<0.0001 ? 0.0001 : step); } void SetSARMaximum(const double max) { this.m_sar_max =(max <0.0001 ? 0.0001 : max); } //--- return Parabolic SAR parameters double SARStep(void) const { return this.m_sar_step; } double SARMaximum(void) const { return this.m_sar_max; } //--- create Parabolic SAR indicator and return the result bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum); //--- constructors CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2); } CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset); //--- destructor ~CTrailingBySAR(){} };
En la sección privada de la clase se declararán las variables para almacenar los valores de los parámetros del indicador Parabolic SAR.
La sección pública contendrá los métodos para establecer y retornar los valores de las propiedades del indicador, un método para crear el indicador y dos constructores: por defecto y paramétrico.
En el constructor por defecto, se creará un indicador en el símbolo y periodo actuales del gráfico, y en el trailing se establecerán valores cero para sus parámetros.
En el constructor paramétrico todos los parámetros se transmitirán a través de las variables de entrada del constructor y luego se creará el indicador sobre los parámetros transmitidos al constructor:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, sar_step, sar_maximum); }
El método Initialize() creará el indicador Parabolic SAR y retornará el resultado de su creación:
//+------------------------------------------------------------------+ //| create Parabolic SAR indicator and return the result | //+------------------------------------------------------------------+ bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetSARStep(sar_step); this.SetSARMaximum(sar_maximum); ::ResetLastError(); this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
Vamos a poner a prueba la clase de trailing creada según Parabolic SAR.
Para la prueba, tomaremos el asesor experto \MQL5\Experts\Advisors\ExpertMACD.mq5 del paquete estándar y lo guardaremos con el nuevo nombre ExpertMACDWithTrailingBySAR.mq5 y añadiremos las líneas necesarias de código.
Luego conectaremos el archivo con las clases de trailing, añadiremos las nuevas variables de entrada y declaremos la instancia de la clase de trailing:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By SAR Parameters -" input ENUM_TIMEFRAMES InpTimeframeSAR = PERIOD_CURRENT; // Parabolic SAR Timeframe input double InpStepSAR = 0.02; // Parabolic SAR Step input double InpMaximumSAR = 0.2; // Parabolic SAR Maximum input int InpTrailingStart = 0; // Trailing Start input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingBySAR ExtTrailing;
En el manejador OnInit(), inicializaremos el trailing según Parabolic SAR:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR)) { ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); } //--- Initializing expert
En el manejador OnTick(), iniciaremos el trailing:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); ExtTrailing.Run(); }
Luego compilaremos el asesor experto y lo ejecutaremos en el simulador con los parámetros por defecto:
Podemos ver como los stops de las posiciones se arrastran correctamente según los valores de la primera barra de Parabolic SAR.
Ahora crearemos las clases de trailing para las diferentes medias móviles representadas en el terminal de cliente.
Clases de trailing según las medias móviles
Estas clases diferirán de la clase de trailing según Parabolic SAR únicamente en el conjunto de parámetros de entrada y por el hecho de que el indicador correspondiente a la clase se creará en el método Initialise().
Partiendo de esta base, consideraremos solo una clase: la clase de media móvil adaptativa:
//+------------------------------------------------------------------+ //| Adaptive Moving Average position StopLoss trailing class | //+------------------------------------------------------------------+ class CTrailingByAMA : public CTrailingByInd { private: int m_period; // Period AMA parameter int m_fast_ema; // Fast EMA Period parameter int m_slow_ema; // Slow EMA Period parameter int m_shift; // Shift AMA parameter ENUM_APPLIED_PRICE m_price; // Applied Price AMA parameter public: //--- set AMA parameters void SetPeriod(const uint period) { this.m_period=int(period<1 ? 9 : period); } void SetFastEMAPeriod(const uint period) { this.m_fast_ema=int(period<1 ? 2 : period); } void SetSlowEMAPeriod(const uint period) { this.m_slow_ema=int(period<1 ? 30 : period); } void SetShift(const int shift) { this.m_shift = shift; } void SetPrice(const ENUM_APPLIED_PRICE price) { this.m_price = price; } //--- return AMA parameters int Period(void) const { return this.m_period; } int FastEMAPeriod(void) const { return this.m_fast_ema; } int SlowEMAPeriod(void) const { return this.m_slow_ema; } int Shift(void) const { return this.m_shift; } ENUM_APPLIED_PRICE Price(void) const { return this.m_price; } //--- create AMA indicator and return the result bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price); //--- constructors CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE); } CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset); //--- destructor ~CTrailingByAMA(){} }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price); } //+------------------------------------------------------------------+ //| create AMA indicator and return the result | //+------------------------------------------------------------------+ bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetPeriod(period); this.SetFastEMAPeriod(fast_ema); this.SetSlowEMAPeriod(slow_ema); this.SetShift(shift); this.SetPrice(price); ::ResetLastError(); this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema, ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
Esta clase será absolutamente idéntica a la clase de trailing según Parabolic SAR comentada anteriormente. Cada clase de trailing de medias móviles tendrá su propio conjunto de variables para almacenar los parámetros del indicador y los métodos para establecer y devolver los valores correspondientes a estas variables. Todas las clases de trailing según las medias móviles serán idénticas entre sí, y el lector podrá familiarizarse con ellas mirando los códigos en el archivo de clases de trailing Trailings.mqh adjunto a este artículo. Podrá probar el funcionamiento de la clase de trailing según la media móvil simple ejecutando el asesor experto de prueba del archivo ExpertMACDWithTrailingByMA.mq5, también adjunto al final del artículo.
Bien, ya hemos creado las clases de trailing simples y trailings según los indicadores estándar presentados en el terminal, adecuados para especificar los niveles para colocar posiciones Stop Loss.
Pero estos no son todos los tipos de trailing que se pueden crear usando las clases presentadas. Existen trailings que desplazan los stops de la posición a determinados niveles de precio especificados por separado para posiciones largas y cortas. Un ejemplo de este tipo de trailing podría ser un trailing en velas High/Low, o en un indicador fractal, por ejemplo.
Para aplicar este tipo de trailing, deberemos poder especificar por separado para cada tipo de posición un nivel diferente para fijar un Stop Loss. Vamos a crear una clase de este tipo.
Clase de trailing según los valores de nivel Stop Loss especificados
Todo lo que debemos hacer para crear un trailing según los valores especificados es definir las variables en lugar de los parámetros de indicador para registrar en ellas los valores de Stop Loss de las posiciones largas y cortas. Y en lugar de obtener los datos del indicador, bastará con calcular los valores para las posiciones de Stop Loss a partir de los valores especificados en estas variables. Los valores de las variables deberán introducirse desde el programa de control, directamente al llamar al método Run() del trailing.
Vamos a escribir una clase de este tipo:
//+------------------------------------------------------------------+ //| Trailing class based on a specified value | //+------------------------------------------------------------------+ class CTrailingByValue : public CSimpleTrailing { protected: double m_value_sl_long; // StopLoss level for long positions double m_value_sl_short; // StopLoss level for short positions //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- return StopLoss level for (2) long and (2) short positions double StopLossValueLong(void) const { return this.m_value_sl_long; } double StopLossValueShort(void) const { return this.m_value_sl_short; } //--- launch trailing with the specified StopLoss offset from the price bool Run(const double value_sl_long, double value_sl_short); //--- constructors CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {} CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {} //--- destructor ~CTrailingByValue(void){} };
Podemos observar que la clase resultará prácticamente idéntica a todas las clases de trailing según los indicadores que hemos mencionado anteriormente. Pero aquí no hay ningún método Initialise(), ya que no será necesario crear ningún indicador.
En el método virtual que calcula y devuelve el nivel de Stop Loss de una posición seleccionada, los valores de Stop Loss se calcularán a partir de los niveles establecidos para los stops de las posiciones largas y cortas:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(this.m_value_sl_long - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(this.m_value_sl_short + this.m_offset * this.m_point); default : return 0; } }
En el método Run(), a través de sus parámetros formales, transmitiremos a la clase los valores de Stop Loss para las posiciones largas y para las posiciones cortas:
//+------------------------------------------------------------------+ //| Launch trailing with the specified StopLoss offset from the price| //+------------------------------------------------------------------+ bool CTrailingByValue::Run(const double value_sl_long, double value_sl_short) { this.m_value_sl_long =value_sl_long; this.m_value_sl_short=value_sl_short; return CSimpleTrailing::Run(); }
En primer lugar, los valores transmitidos en los parámetros formales se escribirán en las variables de miembro de la clase y, a continuación, se llamará al método Run() de la clase padre. El método virtual GetStopLossValue() sobreescrito en esta clase se llamará desde la clase padre, y los stops de la posición se establecerán en los valores calculados en él.
Conectamos un trailing stop a un asesor experto
Ya hemos hablado antes de cómo conectar un trailing al asesor experto, al probar el trailing según los indicadores. Veamos ahora cómo conectar e iniciar un trailing según los valores transmitidos, es decir, según las velas High y Low.
Luego conectaremos el trailing al asesor experto de la entrega estándar \MQL5\Experts\Advisors\ExpertMACD.mq5. Lo guardaremos con el nuevo nombre ExpertMACDWithTrailingByValue.mq5 y realizaremos las mejoras necesarias.
Después conectaremos el archivo con las clases de trailing al asesor experto, añadiremos las variables de entrada para la configuración del trailing y declararemos una instancia de la clase de trailing según los valores especificados:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By Value Parameters -" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Data Rates Timeframe input uint InpDataRatesIndex = 2; // Data Rates Index for StopLoss input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingStart = 0; // Trailing Start input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingByValue ExtTrailing;
La variable de entrada Data Rates Timeframe será el periodo del gráfico del que se tomarán los precios High para los stops cortos y los precios Low para los stops largos.
La variable de entrada Data Rates Index for StopLoss será el índice de la barra en el gráfico con el Data Rates Timeframe del cual se tomarán los precios High y Low para establecer las posiciones de StopLoss.
En el manejador OnInit() del EA, inicializaremos los parámetros del trailing:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); //--- Initializing expert
Solo se arrastrarán los stops de aquellas posiciones cuyo número mágico coincida con el número mágico del EA, es decir, solo arrastraremos las posiciones abiertas por este EA.
En el manejador OnTick() del asesor, obtendremos los datos de la barra con el índice establecido en la variable de entrada InpDataRatesIndex, e iniciaremos el trailing indicando los precios StopLoss (High y Low de la barra) para las posiciones largas y cortas:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); MqlRates rates[1]={}; if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates)) ExtTrailing.Run(rates[0].low,rates[0].high); }
Esto es todo lo que hay que hacer para conectar el trailing al EA. Como ya puede ver, la única diferencia a la hora de conectar los diferentes trailings a los asesores expertos será la declaración de instancias de diferentes tipos de trailings. Todos los demás pasos para conectar las líneas de trailing serán prácticamente iguales y no deberían plantear preguntas ni dudas.
Vamos a compilar el asesor experto y a ejecutarlo en el modo de prueba visual con la configuración predeterminada:
Podemos observar como los stops de la posición se establecen en los valores High y Low de las velas que tienen un índice en la serie temporal 2.
Hasta qué punto un trailing de este tipo puede ofrecer beneficios en el sistema comercial es ya un caso que debe someterse a pruebas independientes.
Además, cada uno puede crear su propio trailing stop según su propio algoritmo con la ayuda de las clases presentadas en el artículo: aquí el espacio para la investigación es prácticamente ilimitado.
Conclusión
Hoy hemos creado clases de diferentes trailing stop que facilitan la conexión de un trailing stop a cualquier EA. Las clases creadas suponen también un buen conjunto de herramientas para crear trailing stops utilizando nuestros propios algoritmos.
En los ejemplos, solo hemos considerado una forma de trabajar con objetos de clase, creando una instancia de un objeto de clase de trailing. Este enfoque permite definir de antemano en el programa qué tipos de trailings serán necesarios y cuáles deben ser los parámetros de los mismos. Para cada trailing, se crea un objeto en el área global. Este planteamiento tiene su razón de ser: es sencillo y directo. Pero para que la creación dinámica de objetos de trailing usando el operador de creación de nuevos objetos sea correcta en cuanto al tiempo de ejecución, será mejor utilizar las facilidades que ofrece la Biblioteca Estándar para la creación de listas de objetos vinculadas. Para ello, todas las clases de trailing se heredan del objeto básico CObject de la biblioteca estándar. Este enfoque puede discutirse en los comentarios al artículo, ya que queda fuera del alcance de este tema.
Todas las clases presentadas en este artículo pueden utilizarse "tal cual" en nuestros propios desarrollos, o podemos modificarlas para adaptarlas a nuestras necesidades y tareas.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/14862





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso