English Русский 中文 Deutsch 日本語 Português
Implementación de Take Profit en forma de órdenes limitadas sin cambiar el código fuente del EA

Implementación de Take Profit en forma de órdenes limitadas sin cambiar el código fuente del EA

MetaTrader 5Ejemplos | 24 diciembre 2018, 11:39
2 295 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Contenido

Introducción

En varios foros, los usuarios critican MetaTrader 5 por su ejecución del mercado de los niveles del Take Profit (TP). Los comentarios de este tipo también se puede encontrar en el foro de este sitio web. Los usuarios escriben sobre el impacto negativo del deslizamiento en el resultado financiero de la operación durante la ejecución del TP. Como una opción, se propone usar las órdenes limitadas para reemplazar el TP estándar.

Por otro lado, el uso de órdenes limitadas, a diferencia del TP estándar de la posición, permite al usuario construir un algoritmo del cierre parcial o paso a paso de la posición, ya que en la orden limitada, Usted puede especificar un volumen diferente del volumen de la posición. En este artículo, me gustaría proponerles una de posibles variantes de la implementación de la semejante sustitución del TP.

1. Aspectos generales

Probablemente, no tiene sentido entrar en polémica «¿Qué es mejor: el algoritmo del trabajo del Take Profit incorporado en MetaTrader 5 o su reemplazo con órdenes pendientes?». Creo que cada uno debe solucionar este problema personalmente basándose en los principios y requerimientos de la estrategia que utiliza. En este artículo, solamente me gustaría proponer una de las posibles soluciones.

Antes de empezar a desarrollar el sistema de órdenes pendientes, vamos a considerar algunos aspectos teóricos que tendremos que implementar en su algoritmo.

Lo importante que tenemos que recordar es que TP es una orden que cierra una posición. Podría parecer que eso se justifica por sí mismo. Y todos están acostumbrados a que el terminal y el sistema se encargan de eso. Pero como hemos decidido reemplazar el sistema en el proceso de la colocación del TP, entonces somos nosotros quienes debemos responsabilizarse de su mantenimiento.

Pues bien, ¿de qué se trata? El cierre de las posiciones puede realizarse no sólo por TP, sino también por SL, o bien a voluntad del trader (incluyendo algunos EAs por los precios del mercado). Por tanto, nuestro sistema tiene que monitorear la presencia de la posición acompañada en el mercado, y eliminar inmediatamente la orden limitada en caso si la posición está ausente por alguna razón. En caso contrario, existe una probabilidad bastante alta de abrir una posición no deseada, causando unas pérdidas mucho más grandes en comparación con el desplazamiento durante la activación del TP estándar.

Además, tenemos que comprender que existe la probabilidad del cierre parcial de la posición, y del aumento de la posición (en las cuentas del netting). Por eso, es importante monitorear no sólo la presencia de la posición, sino también su volumen. Si se cambia el volumen de la posición, es necesario reemplazar inmediatamente la orden limitada.

El siguiente aspecto concierne al funcionamiento del sistema de hedging. En este sistema, el cálculo de posiciones se realiza por separado, y se permite la presencia de varias posiciones para el mismo símbolo al mismo tiempo. De eso se desprende que el disparo de nuestra orden limitada no sólo no cerrará la posición existente, sino abrirá una nueva. Por tanto, después de la activación de la orden limitada, tendremos que ejecutar la operación del cierre opuesto de las posiciones opuestas.

Otro momento importante es el Take Profit de órdenes pendientes. En este caso, tenemos que tomar medidas para que el TP de la orden no dispare antes del procesamiento de la orden. A primera vista, se presenta la posibilidad de usar una orden Stop Limit. Por ejemplo, podemos colocar una orden Stop de venta y una orden Stop Limit de compra simultáneamente. Pero el sistema no nos permitirá ejecutar semejante operación con una orden limitada de venta. Entonces, aquí surge el problema del monitoreo de la activación de la orden pendiente, con la posterior colocación del TP limitado. En su lugar, el monitoreo de su activación dentro del programa y la colocación de la orden pendiente sin TP supone los riesgos de una posible apertura de la posición en el momento cuando nuestro programa no realiza el control por alguna razón. Como resultado, el precio puede alcanzar el nivel esperado de TP y e invertirse. La falta del control por parte del programa no permitirá cerrar la posición, lo que conllevará las pérdidas en el futuro.

Mi solución personal consiste en colocar las órdenes pendientes especificando TP estándar, y sólo después de que se abra la posición, reemplazar TP por una orden limitada mediante la colocación de la orden limitada y el establecimiento del campo de TP a cero. Esta opción nos asegura contra la pérdida del control de la situación. Si se pierde la conexión entre el programa y el servidor, el TP de la orden será activado por el sistema. En este caso, las posibles pérdidas causadas por el deslizamiento negativo son menores que las pérdidas causadas por la falta del control de la situación.

Otro momento es el cambio del TP colocado anteriormente. A menudo, cuando se utilizan diferentes estrategias, hace falta monitorear y ajustar el TP de la posición abierta. Aquí, tenemos dos opciones:

  1. Si hacemos cambios en el código de este EA, entonces, con el fin de no buscar todas las posibles opciones del cambio del TP en el código, simplemente reemplazamos la llamada a la función OrderSend por la llamada al método de nuestra clase, donde ya comprobamos la presencia de la orden limitada colocada anteriormente y su correspondencia a su nuevo nivel. Si es necesario, cambiamos la orden colocada antes, o ignoramos el comando si la orden limitada colocada antes corresponde a nuevos requerimientos.
  2. Usamos el EA adquirido y no tenemos acceso a su código; nuestro programa no abre la posición, sólo reemplaza el TP. En este caso, es muy probable que TP aparezca en una posición que ya tiene colocadas las órdenes limitadas. Eso quiere decir que tenemos que comprobar de nuevo las órdenes limitadas existentes respecto a su relevancia y ajustarlas, y poner el campo del TP a cero.

A parte de eso, tenemos que monitorear la distancia mínima de colocación de las órdenes pendientes desde el precio actual y la distancia de congelación de operaciones comerciales, colocadas por el bróker. Y si lo primero se refiere de forma igual a la colocación del TP de sistema, la distancia de congelación de operaciones comerciales puede hacer «una mala pasada», cuando se cierra la posición por mercado cerca de la orden limitada, haciendo imposible su eliminación o modificación. Lamentablemente, es necesario tomar en cuenta este riesgo no sólo cuando se construye el sistema, sino también cuando se usa, como independiente de su algoritmo.

2. Principios para implementar la vinculación «posición - orden limitada»

Como ya he mencionado antes, es necesario monitorear el estado de la posición y adaptarle el TP limitado. Vamos a ver cómo podemos implementar eso. En primer lugar, es necesario determinar el momento en el que necesitamos realizar este control para no sobrecargar el terminal.

Potencialmente, la posición puede cambiarse en cualquier momento, cuando la sesión comercial está abierta. En realidad, eso no ocurre con tanta frecuencia, mientras que la verificación en cada tick aumenta significativamente el número de operaciones ejecutadas por el EA. Entonces, aquí podemos acudir a los eventos. Según la documentación de MQL5, «el evento Trade se genera cuando la operación comercial se completa en el servidor comercial». Como resultado de este evento, se inicia la función OnTrade. Por consiguiente, el inicio de la verificación de la correspondencia entre las posiciones abiertas y los TP limitados colocados se realiza desde esta función. Eso nos permite no verificar la correspondencia en cada tick, y al mismo tiempo, no perder ningunos cambios.

Luego, surge el problema de la identificación. A primera vista, todo es muy simple. Simplemente verificamos las órdenes limitadas y las posiciones abiertas. Pero nosotros queremos diseñar un algoritmo universal que va a trabajar bien en diferentes tipos de cuentas y con diferentes estrategias. Tampoco hay que olvidar que se puede usar las órdenes limitadas dentro de la estrategia. Por tanto, tenemos que destacar los TP limitados. Para su identificación yo propongo usar los comentarios. Puesto que nuestras órdenes representan el reemplazo del TP, añadimos «TP» al principio del comentario de la orden para su identificación y facilidad de la percepción. Luego, añadimos el número del paso si se usa el cierre gradual de la posición. Eso sería suficiente para el sistema de netting, pero también existe el sistema de hedging con su posibilidad de abrir varias posiciones en la misma cuenta. Por tanto, añadimos el identificador de la posición correspondiente al comentario de nuestro TP limitado.

3. Creando la clase del Take Profit limitado

Vamos a resumir lo expuesto. Podemos dividir la funcionalidad de nuestra clase en dos procesos lógicos:

  1. Introducción de los cambios en el envío de órdenes comerciales.
  2. Monitoreo y corrección de posiciones abiertas y órdenes limitadas colocadas.

Para facilitar el uso, vamos a presentar nuestro algoritmo como la clase CLimitTakeProfit, donde todas las funciones serán estáticas, lo que permitirá usar los métodos de la clase sin declarar su instancia en el código del programa.

class CLimitTakeProfit : public CObject
  {
private:
   static CSymbolInfo       c_Symbol;
   static CArrayLong        i_TakeProfit; //fixed take profit
   static CArrayDouble      d_TakeProfit; //percent to close at take profit
   
public:
                     CLimitTakeProfit();
                    ~CLimitTakeProfit();
//---
   static void       Magic(int value)  {  i_Magic=value; }
   static int        Magic(void)       {  return i_Magic;}
//---
   static void       OnlyOneSymbol(bool value)  {  b_OnlyOneSymbol=value;  }
   static bool       OnlyOneSymbol(void)        {  return b_OnlyOneSymbol; }
//---
   static bool       OrderSend(const MqlTradeRequest &request, MqlTradeResult &result);
   static bool       OnTrade(void);
   static bool       AddTakeProfit(uint point, double percent);
   static bool       DeleteTakeProfit(uint point);
   
protected:
   static int        i_Magic;          //Magic number to control
   static bool       b_OnlyOneSymbol;  //Only position of one symbol under control
//---
   static bool       SetTakeProfits(ulong position_ticket, double new_tp=0);
   static bool       SetTakeProfits(string symbol, double new_tp=0);
   static bool       CheckLimitOrder(MqlTradeRequest &request);
   static void       CheckLimitOrder(void);
   static bool       CheckOrderInHistory(ulong position_id, string comment, ENUM_ORDER_TYPE type, double &volume, ulong call_position=0);
   static double     GetLimitOrderPriceByComment(string comment);
  };

Magic, OnlyOneSymbol, AddTakeProfit y DeleteTakeProfit son los métodos del ajuste del trabajo de la clase. Magic es el número mágico que se usa para monitorear las posiciones (se aplica a las cuentas de hedging), si se indica "-1", la clase va a trabajar con todas las posiciones. OnlyOneSymbol da instrucciones a la clase para trabajar sólo con las posiciones del instrumento del gráfico en el que está iniciado el EA. Los métodos AddTakeProfit y DeteleTakeProfit sirven para añadir y eliminar los niveles del TP fijo con indicación del volumen a cerrar en por cientos del volumen inicial de la posición.

El usuario puede usar estos métodos si desea, pudiendo omitirlos. Por defecto, el método va a trabajar con todos los números mágicos y con todos los instrumentos sin colocar los TP fijos. La orden limitada va a colocarse sólo en vez del TP indicado en la posición.

3.1. Introduciendo cambios en el envío de órdenes comerciales

El método OrderSend monitorea las órdenes enviadas por el EA. No ha sido hecho por casualidad que el nombre y la forma de la llamada a este método son similares a la función estándar del envío de las órdenes en MQL5. Este enfoque simplifica la inserción de nuestro algoritmo en el código del EA diseñado antes, mediante el reemplazo de la función estándar por nuestro método.

Antes ya hemos descrito el reemplazo del TP para las órdenes pendientes. Por esta razón, en este bloque, podremos reemplazar el TP sólo para las órdenes de mercado. Tenga presente, la acepción de la orden por el servidor no significa en absoluto su ejecución. Además de eso, después del envío de la orden, recibiremos el ticket de la orden, pero no recibiremos el identificador de la posición. Por eso, vamos a reemplazar el TP en el bloque del monitoreo, mientras que aquí, sólo vamos a monitorear el momento del cambio del TP colocado antes.

Al principio del código del método, verificamos si la solicitud enviada corresponde a los filtros establecidos para el funcionamiento de nuestro algoritmo. Aparte de eso, comprobaremos el tipo de la operación comercial. Tiene que corresponder a la solicitud del cambio de los niveles Stop de la posición. Además, no olvide comprobar si TP está presente en la solicitud. Si la solicitud no satisface por lo menos uno de los requerimientos, se envía inmediatamente al servidor sin alteraciones.

Después de la comprobación de los requerimientos, la solicitud se pasa al método SetTakeProfit, donde se realiza la colocación de las órdenes limitadas. Nótese que en la clase hay dos métodos semejantes para el trabajo por el ticket de la posición y por el símbolo. El segundo método se aplica más a las cuentas de netting si en la solicitud no figura el ticket de la posición. Si este método se ejecuta con éxito, ponemos el campo del Take Profit a cero.

Puesto que en la solicitud se ha podido cambiar no sólo el TP, sino también el SL, comprobamos la correspondencia del SL y TP establecidos en la posición. Si es necesario, enviamos la solicitud al servidor y salimos de la función. El código completo se muestra a continuación.

bool CLimitTakeProfit::OrderSend(MqlTradeRequest &request,MqlTradeResult &result)
  {
   if((b_OnlyOneSymbol && request.symbol!=_Symbol) ||
      (i_Magic>=0 && request.magic!=i_Magic) || !(request.action==TRADE_ACTION_SLTP && request.tp>0))
      return(::OrderSend(request,result));
//---
   if(((request.position>0 && SetTakeProfits(request.position,request.tp)) ||
       (request.position<=0 && SetTakeProfits(request.symbol,request.tp))) && request.tp>0)
      request.tp=0;
   if((request.position>0 && PositionSelectByTicket(request.position)) ||
      (request.position<=0 && PositionSelect(request.symbol)))
     {
      if(PositionGetDouble(POSITION_SL)!=request.sl || PositionGetDouble(POSITION_TP)!=request.tp)
         return(::OrderSend(request,result)); 
     }
//---
   return true;
  }

Ahora, vamos a analizar el método SetTakeProfit más detalladamente. Al principio del método, comprobamos si la posición especificada está presente y si trabajamos con el símbolo de la posición. Luego, actualizamos los datos del instrumento de la posición. Después de eso calculamos los precios más cercanos en los cuales las órdenes limitadas estarán permitidas. En caso de algún error, salimos fuera del método con el resultado false.

bool CLimitTakeProfit::SetTakeProfits(ulong position_ticket, double new_tp=0)
  {
   if(!PositionSelectByTicket(position_ticket) || (b_OnlyOneSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol))
      return false;
   if(!c_Symbol.Name(PositionGetString(POSITION_SYMBOL)) || !c_Symbol.Select() || !c_Symbol.Refresh() || !c_Symbol.RefreshRates())
      return false;
//---
   double min_sell_limit=c_Symbol.NormalizePrice(c_Symbol.Ask()+c_Symbol.StopsLevel()*c_Symbol.Point());
   double max_buy_limit=c_Symbol.NormalizePrice(c_Symbol.Bid()-c_Symbol.StopsLevel()*c_Symbol.Point());

Después de eso, preparamos las plantillas de las estructuras para enviar la solicitud comercial para la colocación de la orden limitada. Calculamos el tamaño del TP a colocar (o ya colocado en la posición) para usar en el futuro sólo los TP fijos que no salen fuera de la distancia calculada.

   MqlTradeRequest tp_request={0};
   MqlTradeResult tp_result={0};
   tp_request.action =  TRADE_ACTION_PENDING;
   tp_request.magic  =  PositionGetInteger(POSITION_MAGIC);
   tp_request.type_filling =  ORDER_FILLING_RETURN;
   tp_request.position=position_ticket;
   tp_request.symbol=c_Symbol.Name();
   int total=i_TakeProfit.Total();
   double tp_price=(new_tp>0 ? new_tp : PositionGetDouble(POSITION_TP));
   if(tp_price<=0)
      tp_price=GetLimitOrderPriceByComment("TPP_"+IntegerToString(position_ticket));
   double open_price=PositionGetDouble(POSITION_PRICE_OPEN);
   int tp_int=(tp_price>0 ? (int)NormalizeDouble(MathAbs(open_price-tp_price)/c_Symbol.Point(),0) : INT_MAX);
   double position_volume=PositionGetDouble(POSITION_VOLUME);
   double closed=0;
   double closed_perc=0;
   double fix_closed_per=0;

Luego organizamos el ciclo para comprobar y colocar los TP fijos. Primero, establecemos el comentario de nuestra orden (el principio de la codificación se discutía más arriba). Luego nos aseguramos de que el nivel del TP fijo establecido en la posición o la solicitud no supere éste. Si lo supera, pasamos al siguiente TP. Además, asegúrese de que el volumen de las órdenes limitadas colocadas antes no sobrepase el volumen de la posición. Si las órdenes limitadas sobrepasan el volumen de la posición, salimos del ciclo.

   for(int i=0;i<total;i++)
     {
      tp_request.comment="TP"+IntegerToString(i)+"_"+IntegerToString(position_ticket);
      if(i_TakeProfit.At(i)<tp_int && d_TakeProfit.At(i)>0)
        {
         if(closed>=position_volume || fix_closed_perc>=100)
            break;

En el siguiente paso llenamos los elementos que faltan de la estructura de la solicitud comercial. Para eso, calculamos el volumen de la nueva orden limitada y especificamos el tipo de la orden y el precio de su apertura.

//---
         double lot=position_volume*MathMin(d_TakeProfit.At(i),100-closed)/(100-fix_closed_perc);
         lot=MathMin(position_volume-closed,lot);
         lot=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((lot-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
         lot=NormalizeDouble(lot,2);
         tp_request.volume=lot;
         switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_BUY:
              tp_request.type=ORDER_TYPE_SELL_LIMIT;
              tp_request.price=c_Symbol.NormalizePrice(open_price+i_TakeProfit.At(i)*c_Symbol.Point());
              break;
            case POSITION_TYPE_SELL:
              tp_request.type=ORDER_TYPE_BUY_STOP;
              tp_request.price=c_Symbol.NormalizePrice(open_price-i_TakeProfit.At(i)*c_Symbol.Point());
              break;
           }

Después de llenar la estructura de la solicitud comercial, comprobamos si la orden limitada con estos parámetros ha sido colocado antes o no. Para eso, usamos el método CheckLimitOrder (analizaremos el algoritmo del método más abajo), pasándole la estructura de la solicitud rellenada. Si la orden ha sido colocada antes, añadimos el volumen colocado de la orden a la suma de los volúmenes colocados para la posición. Esta operación es necesaria para controlar la correspondencia de los volúmenes de la posición y de las órdenes limitadas colocadas.

         if(CheckLimitOrder(tp_request))
           {
            if(tp_request.volume>=0)
              {
               closed+=tp_request.volume;
               closed_perc=closed/position_volume*100;
              }
            else
              {
               fix_closed_per-=tp_request.volume/(position_volume-tp_request.volume)*100;
              }
            continue;
           }

Si la orden no ha sido colocada todavía, ajustamos su precio de acuerdo con los requerimientos del bróker respecto al precio actual y enviamos la solicitud al servidor. Si la solicitud ha sido enviada con éxito, sumamos el volumen colocado de la orden con la suma de los volúmenes colocados antes para la posición.

         switch(tp_request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              tp_request.price=MathMin(tp_request.price,max_buy_limit);
              break;
            case  ORDER_TYPE_SELL_LIMIT:
              tp_request.price=MathMax(tp_request.price,min_sell_limit);
              break;
           }
         if(::OrderSend(tp_request,tp_result))
           {
            closed+=tp_result.volume;
            closed_perc=closed/position_volume*100;
            ZeroMemory(tp_result);
           }
        }
     }

Después de terminar el ciclo, usamos el mismo algoritmo para colocar la orden limitada por el volumen que falta y por el precio especificado en la solicitud modificante (o en la posición). Si el volumen es menor que el volumen mínimo permitido, salimos fuera de la función con el resultado false.

   if(tp_price>0 && position_volume>closed)
     {
      tp_request.price=tp_price;
      tp_request.comment="TPP_"+IntegerToString(position_ticket);
      tp_request.volume=position_volume-closed;
      if(tp_request.volume<c_Symbol.LotsMin())
         return false;
      tp_request.volume=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((tp_request.volume-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
      tp_request.volume=NormalizeDouble(tp_request.volume,2);
//---
      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           tp_request.type=ORDER_TYPE_SELL_LIMIT;
           break;
         case POSITION_TYPE_SELL:
           tp_request.type=ORDER_TYPE_BUY_LIMIT;
           break;
        }
      if(CheckLimitOrder(tp_request) && tp_request.volume>=0)
        {
         closed+=tp_request.volume;
         closed_perc=closed/position_volume*100;
        }
      else
        {
         switch(tp_request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              tp_request.price=MathMin(tp_request.price,max_buy_limit);
              break;
            case  ORDER_TYPE_SELL_LIMIT:
              tp_request.price=MathMax(tp_request.price,min_sell_limit);
              break;
           }
         if(tp_request.volume<=0)
           {
            tp_request.volume=position_volume-closed;
            tp_request.volume=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((tp_request.volume-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
            tp_request.volume=NormalizeDouble(tp_request.volume,2);
           }
         if(::OrderSend(tp_request,tp_result))
           {
            closed+=tp_result.volume;
            closed_perc=closed/position_volume*100;
            ZeroMemory(tp_result);
           }
        }
     }      

Al final del método, comprobamos si el volumen de las órdenes limitadas colocadas cubre el volumen de las posiciones. Si es así, ponemos el valor del TP en la posición a cero y salimos de la función.

   if(closed>=position_volume && PositionGetDouble(POSITION_TP)>0)
     {
      ZeroMemory(tp_request);
      ZeroMemory(tp_result);
      tp_request.action=TRADE_ACTION_SLTP;
      tp_request.position=position_ticket;
      tp_request.symbol=c_Symbol.Name();
      tp_request.sl=PositionGetDouble(POSITION_SL);
      tp_request.tp=0;
      tp_request.magic=PositionGetInteger(POSITION_MAGIC);
      if(!OrderSend(tp_request,tp_result))
         return false;
     }
   return true;
  }

Para hacer la imagen más completa, amos a analizar el algoritmo del método CheckLimitOrder. Este método verifica la presencia de la orden limitada colocada anteriormente según la solicitud comercial preparada. Si la orden ya está colocada, el método devuelve true y la nueva orden no se coloca.

Al principio del método, definimos los posibles niveles más cercanos para colocar las órdenes limitadas. Los vamos a necesitar si surge la necesidad de modificar la orden colocada anteriormente.

bool CLimitTakeProfit::CheckLimitOrder(MqlTradeRequest &request)
  {
   double min_sell_limit=c_Symbol.NormalizePrice(c_Symbol.Ask()+c_Symbol.StopsLevel()*c_Symbol.Point());
   double max_buy_limit=c_Symbol.NormalizePrice(c_Symbol.Bid()-c_Symbol.StopsLevel()*c_Symbol.Point());

En el siguiente paso organizamos el ciclo del repaso de todas las órdenes abiertas, entre las cuales vamos a identificar la orden necesarias por su comentario.

   for(int i=0;i<total;i++)
     {
      ulong ticket=OrderGetTicket((uint)i);
      if(ticket<=0)
         continue;
      if(OrderGetString(ORDER_COMMENT)!=request.comment)
         continue;

Si encontramos la orden con el comentario necesario, comprobamos su volumen y su tipo. Si uno de los parámetros no coincide, eliminamos la orden pendiente existente y salimos de la función con el resultado false. Si surge un error durante la eliminación de la orden, ponemos el volumen de la orden existente en el campo del volumen de la solicitud.

      if(OrderGetDouble(ORDER_VOLUME_INITIAL) != request.volume || OrderGetInteger(ORDER_TYPE)!=request.type)
        {
         MqlTradeRequest del_request={0};
         MqlTradeResult del_result={0};
         del_request.action=TRADE_ACTION_REMOVE;
         del_request.order=ticket;
         if(::OrderSend(del_request,del_result))
            return false;
         request.volume=OrderGetDouble(ORDER_VOLUME_INITIAL);
        }

En el siguiente paso, comprobamos el precio de apertura de la orden encontrada y de la especificada en los parámetros. Si hace falta, modificamos la orden actual y salimos del método con el resultado true.

      if(MathAbs(OrderGetDouble(ORDER_PRICE_OPEN)-request.price)>=c_Symbol.Point())
        {
         MqlTradeRequest mod_request={0};
         MqlTradeResult mod_result={0};
         mod_request.action=TRADE_ACTION_MODIFY;
         mod_request.price=request.price;
         mod_request.magic=request.magic;
         mod_request.symbol=request.symbol;
         switch(request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              if(mod_request.price>max_buy_limit)
                 return true;
              break;
            case ORDER_TYPE_SELL_LIMIT:
              if(mod_request.price<min_sell_limit)
                 return true;
              break;
           }
         bool mod=::OrderSend(mod_request,mod_result);
        }
      return true;
     }

Sin embargo, no vamos olvidar de una posible situación cuando la orden limitada con el mismo volumen ya ha sido activada. Por eso, si la orden que se busca no ha sido encontrada entre las órdenes abiertas, también hay que comprobar el historial de las órdenes de esta posición. Esta funcionalidad está implementada en el método CheckOrderInHistory que vamos a invocar al final.

   if(!PositionSelectByTicket(request.position))
      return true;
//---
   return CheckOrderInHistory(PositionGetInteger(POSITION_IDENTIFIER),request.comment, request.type, request.volume);
  }

Dependiendo del tipo de la cuenta, tenemos dos posibles opciones de la activación de la orden limitada:

  1. Activación directa en la posición (cuantas de netting).
  2. La orden limitada abre una posición opuesta, y luego, las posiciones se cierran por las posiciones opuestas (cuentas de hedging).

Cuando se busca el cierre opuesto de las posiciones, nótese que las órdenes del cierre opuesto pueden no pertenecer a esta posición. Por eso, vamos a realizar el repaso por las transacciones, y obtener el ticket de la orden de la transacción.

bool CLimitTakeProfit::CheckOrderInHistory(ulong position_id, string comment, ENUM_ORDER_TYPE type, double &volume, ulong call_position=0)
  {
   if(!HistorySelectByPosition(position_id))
      return true;
   int total=HistoryDealsTotal();
   bool hedging=(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//---
   for(int i=0;i<total;i++)
     {
      ulong ticket=HistoryDealGetTicket((uint)i);
      ticket=HistoryDealGetInteger(ticket,DEAL_ORDER);
      if(!HistoryOrderSelect(ticket))
         continue;
      if(ticket<=0)
         continue;

Para las cuentas de hedging, primero comprobamos si la orden pertenece a otra posición o no. Si encontramos la orden de otra posición, vamos a buscar la orden con el comentario que se busca en esta posición. Para eso, realizaremos la llamada recursiva de la función CheckOrderInHistory. Para evitar el efecto del ciclo, antes de la llamada al método, no olvidamos comprobar si el método ha sido invocado de esta posición o no. Si la orden ha sido encontrada, salimos del método con el resultado true. De lo contrario, volvemos a cargar el historial de nuestra posición y pasamos a la siguiente transacción.

      if(hedging && HistoryOrderGetInteger(ticket,ORDER_POSITION_ID)!=position_id && HistoryOrderGetInteger(ticket,ORDER_POSITION_ID)!=call_position)
        {
         if(CheckOrderInHistory(HistoryOrderGetInteger(ticket,ORDER_POSITION_ID),comment,type,volume))
            return true;
         if(!HistorySelectByPosition(position_id))
            continue;
        }

Comprobamos el comentario y el tipo de la orden para las órdenes de la posición actual. Si la orden ha sido encontrada, escribimos su volumen en la solicitud con el signo menos y salimos del método.

      if(HistoryOrderGetString(ticket,ORDER_COMMENT)!=comment)
         continue;
      if(HistoryOrderGetInteger(ticket,ORDER_TYPE)!=type)
         continue;
//---
      volume=-OrderGetDouble(ORDER_VOLUME_INITIAL);
      return true;
     }
   return false;
  }

El código completo de todos los métodos y funciones se adjuntan al artículo.

3.2. Procesando operaciones comerciales

El segundo bloque de nuestro algoritmo se encarga del monitoreo y corrección de posiciones existentes y órdenes limitadas abiertas.

La ejecución de las operaciones comerciales en la cuenta se genera por el evento Trade, el que, a su vez, causa la ejecución de la función OnTrade. Añadimos el método correspondiente a nuestra clase para procesar las operaciones comerciales.

El algoritmo del método se empieza con el trabajo preparativo: obtenemos el número de posiciones abiertas en la cuenta y verificamos el tipo de la cuenta.

bool CLimitTakeProfit::OnTrade(void)
  {
   int total=PositionsTotal();
   bool result=true;
   bool hedhing=AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;

Luego organizamos el ciclo para repasar las posiciones abiertas. Al principio del ciclo, comprobamos la correspondencia de las posiciones a las condiciones de la filtración por el instrumento y por el número mágico (para las cuentas de hedging).

   for(int i=0;i<total;i++)
     {
      ulong ticket=PositionGetTicket((uint)i);
      if(ticket<=0 || (b_OnlyOneSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol))
         continue;
//---
     if(i_Magic>0)
        {
         if(hedhing && PositionGetInteger(POSITION_MAGIC)!=i_Magic)
            continue;
        }

Luego, para las cuentas de hedging, comprobamos si la posición ha sido abierta para procesar nuestro TP limitado. Si es así, ejecutamos la operación del cierre opuesto de la posición. Después cerrar las posiciones con éxito, pasamos a la siguiente posición.

      if(mi.TypeMenuItem()!=MI_HAS_CONTEXT_MENU)
        {
         string comment=PositionGetString(POSITION_COMMENT);
         if(StringFind(comment,"TP")==0)
           {
            int start=StringFind(comment,"_");
            if(start>0)
              {
               long ticket_by=StringToInteger(StringSubstr(comment,start+1));
               long type=PositionGetInteger(POSITION_TYPE);
               if(ticket_by>0 && PositionSelectByTicket(ticket_by) && type!=PositionGetInteger(POSITION_TYPE))
                 {
                  MqlTradeRequest   request  ={0};
                  MqlTradeResult    trade_result   ={0};
                  request.action=TRADE_ACTION_CLOSE_BY;
                  request.position=ticket;
                  request.position_by=ticket_by;
                  if(::OrderSend(request,trade_result))
                     continue;
                 }
              }
           }
        }

Al final del ciclo, llamamos al método SetTakeProfits para verificar y colocar las órdenes pendientes para las posiciones. El algoritmo del método ha sido escrito antes.

      result=(SetTakeProfits(PositionGetInteger(POSITION_TICKET)) && result);
     }

Después de finalizar el ciclo de la verificación de posiciones abiertas, comprobamos la correspondencia de las órdenes limitadas activas a las posiciones abiertas. Si es necesario, eliminamos las órdenes limitadas que se han quedado de las posiciones cerradas. Para eso, llamamos al método CheckLimitOrder. Nótese, en este caso, la función se invoca sin parámetros a diferencia de la llamada a la función descrita más arriba. Eso se debe a que llamamos al método absolutamente diferente, mientras que la aplicación del nombre similar es posible gracias a la propiedad sobrecarga de la función.

   CheckLimitOrder();
//---
   return result;
  }

El algoritmo de este método se basa en la iteración de todas las órdenes colocadas. Nuestras órdenes se seleccionan usando los comentarios.

void CLimitTakeProfit::CheckLimitOrder(void)
  {
   int total=OrdersTotal();
   bool res=false;
//---
   for(int i=0;(i<total && !res);i++)
     {
      ulong ticket=OrderGetTicket((uint)i);
      if(ticket<=0)
         continue;
      string comment=OrderGetString(ORDER_COMMENT);
      if(StringFind(comment,"TP")!=0)
         continue;
      int pos=StringFind(comment,"_",0);
      if(pos<0)
         continue;

Si hemos encontrado el TP limitado, extraemos el identificador de la posición opuesta desde el comentario. Usamos este identificador para acceder a la posición especificada. Si la posición existe, eliminamos la orden.

      long pos_ticker=StringToInteger(StringSubstr(comment,pos+1));
      if(!PositionSelectByTicket(pos_ticker))
        {
         MqlTradeRequest del_request={0};
         MqlTradeResult del_result={0};
         del_request.action=TRADE_ACTION_REMOVE;
         del_request.order=ticket;
         if(::OrderSend(del_request,del_result))
           {
            i--;
            total--;
           }
         continue;
        }

Si hemos conseguido acceder a la posición, comprobamos la correspondencia del tipo de la orden al tipo de la posición. Esta verificación es necesaria para las cuentas de netting, donde la reversión de la posición es posible durante las operaciones comerciales. En caso de la falta de la correspondencia, eliminamos la orden y pasamos al chequeo de la siguiente orden.

      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_LIMIT)
              continue;
           break;
         case POSITION_TYPE_SELL:
           if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_LIMIT)
              continue;
           break;
        }
      MqlTradeRequest del_request={0};
      MqlTradeResult del_result={0};
      del_request.action=TRADE_ACTION_REMOVE;
      del_request.order=ticket;
      if(::OrderSend(del_request,del_result))
        {
         i--;
         total--;
        }
     }
//---
   return;
  }

El código completo de todos los métodos de la clase se encuentra en los archivos adjuntos.

4. Integrando la clase en el EA

Después de finalizar el trabajo con la clase, vamos a considerar el mecanismo de su integración en el EA ya diseñado.

Como puede recordar, todos los métodos de nuestra clase son estáticos, lo que permite usarlos sin declarar la instancia de la clase. Este enfoque ha sido elegido originalmente para facilitar la integración de la clase en el EA ya diseñado. En realidad, es el primer paso de la integración de la clase en el EA.

En el segundo paso, creamos la función LimitOrderSend con los parámetros de la llamada similares a la función OrderSend. Va a ubicarse por debajo del código de nuestra clase y su única funcionalidad va a consistir en la llamada al método CLimitTakeProfit::OrderSend. Luego, usamos la directiva #define para reemplazar la función original OrderSend por la nuestra. La aplicación de este método nos permite colocar de golpe nuestro código en todas las funciones del EA que envían las solicitudes comerciales. Así, no tendremos que gastar tanto tiempo para buscar estos comandos en el código entero del EA.

bool LimitOrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
 { return CLimitTakeProfit::OrderSend(request,result); } 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#define OrderSend(request,result)      LimitOrderSend(request,result)

Puesto que muchos EAs no usan la función OnTrade, también podemos escribirla en el archivo de nuestra clase. Pero si su EA utiliza esta función, Usted tendrá que eliminar u ocultar en los comentarios el código de abajo, y añadir la llamada al método CLimitTakeProfit::OnTrade al cuerpo de la función de su EA.

void OnTrade()
  {
   CLimitTakeProfit::OnTrade();
  }

Luego, para integrar la clase al EA, nos queda añadir la referencia al archivo de nuestra clase usando la directiva #include. Recuerde que nuestra clase tienen que ubicarse antes de la llamada de otras bibliotecas y del código del EA. Más abajo se muestra el ejemplo de cómo añadir la clase al EA MACD Sample.mq5 desde la entrega estándar del terminal.

//+------------------------------------------------------------------+
//|                                          MACD Sample LimitTP.mq5 |
//|                   Copyright 2009-2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2017, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property version     "5.50"
#property description "It is important to make sure that the expert works with a normal"
#property description "chart and the user did not make any mistakes setting input"
#property description "variables (Lots, TakeProfit, TrailingStop) in our case,"
#property description "we check TakeProfit on a chart of more than 2*trend_period bars"

#define MACD_MAGIC 1234502
//---
#include <Trade\LimitTakeProfit.mqh>
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//---

Usted puede añadir el cierre parcial de la posición en el código de la función OnInit. Nuestro EA está listo para el trabajo.

No olvide testear el EA antes de usarlo en las cuentas reales.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- create all necessary objects
   if(!ExtExpert.Init())
      return(INIT_FAILED);
   CLimitTakeProfit::AddTakeProfit(100,50);
//--- secceed
   return(INIT_SUCCEEDED);
  }

Demostración del funcionamiento del Asesor Experto

Encontrará el código completo del EA en los archivos adjuntos.

Conclusiones

En este artículo, se ofrece el mecanismo del reemplazo del Take Profit de la posición por las órdenes limitadas opuestas. Hemos intentado simplificar al máximo la integración del método en el código de cualquier EA existente. Espero que este artículo sea útil, y Usted podrá apreciar todas las ventajas y evaluar las desventajas de ambos métodos.

Los programas usados en el artículo:

#
Nombre
Tipo
Descripción
1LimitTakeProfit.mqhLibrería de la claseClase del reemplazo del TP de la orden por las órdenes limitadas
2MACD Sample.mq5Asesor ExpertoEA original desde los ejemplos de MetaTrader 5
3MACD Sample LimitTP.mq5Asesor ExpertoEjemplo de integración de la clase en el EA desde los ejemplos de MetaTrader 5


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

Archivos adjuntos |
MQL5.zip (184.41 KB)
Métodos de control remoto de EAs Métodos de control remoto de EAs
La principal ventaja de los robots comerciales es su funcionamiento ininterrumpido las 24 horas del día en un servidor VPS remoto. Pero a veces es necesario intervenir en su funcionamiento manualmente, y ahora no disponemos de acceso directo al servidor. ¿Podemos gestionar de forma remota el funcionamiento del asesor? En este artículo se presenta una de las variantes de control de robots a través de comandos externos.
Reversión: ¿es el Santo Grial o una peligrosa equivocación? Reversión: ¿es el Santo Grial o una peligrosa equivocación?
En el presente artículo intentaremos aclarar lo siguiente: ¿qué es una reversión, si merece la pena usarla y si podemos mejorar nuestra estrategia comercial a través de ella? Vamos a crear un Asesor Experto, y veremos en los datos históricos qué indicadores convienen mejor para la reversión, además, si podemos usarla sin indicadores como un sistema comercial independiente. Veremos si es posible convertir un sistema comercial no rentable en un sistema rentable a través de la reversión.
Optimización automática de EAs en MetaTrader 5 Optimización automática de EAs en MetaTrader 5
En este artículo se describe el mecanismo de auto-optimización de un experto que funcione en MetaTrader 5.
Recetas MQL5 – Obteniendo las propiedades de una posición de cobertura abierta Recetas MQL5 – Obteniendo las propiedades de una posición de cobertura abierta
La plataforma MetaTrader 5 no es solo una plataforma multimercado, sino que también permite usar diferentes sistemas de registro de posiciones. Estas posibilidades amplian considerablemente el instrumental para la implementación y formalización de las ideas comerciales. En el artículo, vamos a hablar sobre cómo procesar y considerar las propiedades de las posiciones al llevar su registro de forma independiente ("cobertura"). Así, en el artículo proponemos una clase derivada, mostrando a continuación ejemplos de procesamiento y obtención de las propiedades de la posición de cobertura.