Funciones de lectura de propiedades de posición

Un programa MQL puede obtener propiedades de posición utilizando varias funciones PositionGet dependiendo del tipo de propiedades. En todas las funciones, la propiedad específica que se solicita se define en el primer parámetro, que toma el ID de una de las enumeraciones ENUM_POSITION_PROPERTY comentadas en la sección anterior.

Para cada tipo de propiedad, existe una forma corta y una forma larga de la función: la primera devuelve directamente el valor de la propiedad, y la segunda lo escribe en el segundo parámetro, pasado por referencia.

Las propiedades de enteros y las propiedades de tipos compatibles (datetime, enumeraciones) pueden obtenerse mediante la función PositionGetInteger.

long PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property)

bool PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property, long &value)

Si falla, la función devuelve 0 o false.

La función PositionGetDouble se utiliza para obtener propiedades reales.

double PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property)

bool PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property, double &value)

Por último, la función PositionGetString devuelve las propiedades de las cadenas.

string PositionGetString(ENUM_POSITION_PROPERTY_STRING property)

bool PositionGetString(ENUM_POSITION_PROPERTY_STRING property, string &value)

En caso de fallo, la primera forma de la función devuelve una cadena vacía.

Para leer las propiedades de posición, tenemos ya lista una interfaz abstracta MonitorInterface (TradeBaseMonitor.mqh) que utilizamos para escribir un monitor de órdenes. Ahora será fácil implantar un monitor similar para las posiciones. El resultado se adjunta en el archivo PositionMonitor.mqh.

La clase PositionMonitorInterface se hereda de MonitorInterface con asignación a los tipos de plantilla I, D y S de las enumeraciones consideradas ENUM_POSITION_PROPERTY, y anula un par de métodos de stringify teniendo en cuenta las particularidades de las propiedades de posición.

class PositionMonitorInterface:
   public MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,
   ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
{
public:
   virtual string stringify(const long v,
      const ENUM_POSITION_PROPERTY_INTEGER propertyconst override
   {
      switch(property)
      {
         case POSITION_TYPE:
            return enumstr<ENUM_POSITION_TYPE>(v);
         case POSITION_REASON:
            return enumstr<ENUM_POSITION_REASON>(v);
         
         case POSITION_TIME:
         case POSITION_TIME_UPDATE:
            return TimeToString(vTIME_DATE TIME_SECONDS);
         
         case POSITION_TIME_MSC:
         case POSITION_TIME_UPDATE_MSC:
            return STR_TIME_MSC(v);
      }
      
      return (string)v;
   }
   
   virtual string stringify(const ENUM_POSITION_PROPERTY_DOUBLE property,
      const string format = NULLconst override
   {
      if(format == NULL &&
         (property == POSITION_PRICE_OPEN || property == POSITION_PRICE_CURRENT
         || property == POSITION_SL || property == POSITION_TP))
      {
         const int digits = (int)SymbolInfoInteger(PositionGetString(POSITION_SYMBOL),
            SYMBOL_DIGITS);
         return DoubleToString(PositionGetDouble(property), digits);
      }
      return MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,
         ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
         ::stringify(propertyformat);
   }

La clase específica del monitor, preparada para ver posiciones, es la siguiente en la cadena de herencia y se basa en las funciones de PositionGet. La selección de una posición por ticket se realiza en el constructor.

class PositionMonitorpublic PositionMonitorInterface
{
public:
   const ulong ticket;
   PositionMonitor(const ulong t): ticket(t)
   {
      if(!PositionSelectByTicket(ticket))
      {
         PrintFormat("Error: PositionSelectByTicket(%lld) failed: %s",
            ticketE2S(_LastError));
      }
      else
      {
         ready = true;
      }
   }
   
   virtual long get(const ENUM_POSITION_PROPERTY_INTEGER propertyconst override
   {
      return PositionGetInteger(property);
   }
   
   virtual double get(const ENUM_POSITION_PROPERTY_DOUBLE propertyconst override
   {
      return PositionGetDouble(property);
   }
   
   virtual string get(const ENUM_POSITION_PROPERTY_STRING propertyconst override
   {
      return PositionGetString(property);
   }
   ...
};

Un sencillo script le permitirá registrar todas las características de la primera posición (si hay al menos una disponible).

void OnStart()
{
   PositionMonitor pm(PositionGetTicket(0));
   pm.print();
}

En el registro, deberíamos obtener algo como esto:

MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER, »
   » ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
ENUM_POSITION_PROPERTY_INTEGER Count=9
  0 POSITION_TIME=2022.03.24 23:09:45
  1 POSITION_TYPE=POSITION_TYPE_BUY
  2 POSITION_MAGIC=0
  3 POSITION_IDENTIFIER=1291755067
  4 POSITION_TIME_MSC=2022.03.24 23:09:45'261
  5 POSITION_TIME_UPDATE=2022.03.24 23:09:45
  6 POSITION_TIME_UPDATE_MSC=2022.03.24 23:09:45'261
  7 POSITION_TICKET=1291755067
  8 POSITION_REASON=POSITION_REASON_EXPERT
ENUM_POSITION_PROPERTY_DOUBLE Count=8
  0 POSITION_VOLUME=0.01
  1 POSITION_PRICE_OPEN=1.09977
  2 POSITION_PRICE_CURRENT=1.09965
  3 POSITION_SL=0.00000
  4 POSITION_TP=1.10500
  5 POSITION_COMMISSION=0.0
  6 POSITION_SWAP=0.0
  7 POSITION_PROFIT=-0.12
ENUM_POSITION_PROPERTY_STRING Count=3
  0 POSITION_SYMBOL=EURUSD
  1 POSITION_COMMENT=
  2 POSITION_EXTERNAL_ID=

Si no hay posiciones abiertas en ese momento, veremos un mensaje de error.

Error: PositionSelectByTicket(0) failed: TRADE_POSITION_NOT_FOUND

No obstante, el monitor es útil no sólo y no tanto por la salida de las propiedades en el registro. Basándonos en PositionMonitor, creamos una clase para seleccionar posiciones por condiciones, similar a lo que hicimos para las órdenes (OrderFilter). El objetivo final es mejorar nuestro Asesor Experto de cuadrícula.

Gracias a la programación orientada a objetos (POO), la creación de una nueva clase de filtro no requiere prácticamente ningún esfuerzo. A continuación se muestra el código fuente completo (archivo PositionFilter.mqh).

class PositionFilterpublic TradeFilter<PositionMonitor,
   ENUM_POSITION_PROPERTY_INTEGER,
   ENUM_POSITION_PROPERTY_DOUBLE,
   ENUM_POSITION_PROPERTY_STRING>
{
protected:
   virtual int total() const override
   {
      return PositionsTotal();
   }
   virtual ulong get(const int iconst override
   {
      return PositionGetTicket(i);
   }
};

Ahora podemos escribir un script de este tipo para recibir beneficios específicos en posiciones con el número mágico dado, por ejemplo.

input ulong Magic;
   
void OnStart()
{
   PositionFilter filter;
   
   ENUM_POSITION_PROPERTY_DOUBLE properties[] =
      {POSITION_PROFITPOSITION_VOLUME};
   
   double profits[][2];
   ulong tickets[];
   string symbols[];
   
   filter.let(POSITION_MAGICMagic).select(propertiesticketsprofits);
   filter.select(POSITION_SYMBOLticketssymbols);
   
   for(int i = 0i < ArraySize(symbols); ++i)
   {
      PrintFormat("%s[%lld]=%f",
         symbols[i], tickets[i], profits[i][0] / profits[i][1]);
   }
}

En este caso hemos tenido que llamar al método select dos veces, porque los tipos de propiedades que nos interesan son diferentes: beneficio real y lote, pero el nombre de cadena del instrumento. En una de las secciones al principio del capítulo, cuando estábamos desarrollando la clase de filtro para símbolos, describimos el concepto de tuplas. En MQL5 podemos implementarlo como plantillas de estructura con campos de tipos arbitrarios. Estas tuplas serían muy útiles para finalizar la jerarquía de clases de filtros, ya que entonces sería posible describir el método select que rellena un array de tuplas con campos de cualquier tipo.

Las tuplas se describen en el archivo Tuples.mqh. Todas sus estructuras tienen un nombre TupleN<T1,...>, donde N es un número de 2 a 8, y corresponde al número de parámetros de la plantilla (tipos Ti). Por ejemplo, Tuple2:

template<typename T1,typename T2>
struct Tuple2
{
   T1 _1;
   T2 _2;
   
   static int size() { return 2; };
   
   // M — order, position, deal monitor class, any MonitorInterface<>
   template<typename M>
   void assign(const int &properties[], M &m)
   {
      if(ArraySize(properties) != size()) return;
      _1 = m.get(properties[0], _1);
      _2 = m.get(properties[1], _2);
   }
};

En la clase TradeFilter (TradeFilter.mqh) vamos a añadir una versión de la función select con tuplas.

template<typename T,typename I,typename D,typename S>
class TradeFilter
{
   ...
 template<typename U// type U must be Tuple<>, e.g. Tuple3<T1,T2,T3>
   bool select(const int &property[], U &data[], const bool sort = falseconst
   {
      const int q = ArraySize(property);
      static const U u;                 // PRB: U::size() does not compile
      if(q != u.size()) return false;   // required condition
      
      const int n = total();
      // cycle through orders/positions/deals
      for(int i = 0i < n; ++i)
      {
         const ulong t = get(i);
         // access to properties via monitor T
         T m(t);
         // check all filter conditions for different types of properties
         if(match(mlongs)
         && match(mdoubles)
         && match(mstrings))
         {
            // for a suitable object, store the properties in an array of tuples
            const int k = EXPAND(data);
            data[k].assign(propertym);
         }
      }
      
      if(sort)
      {
         sortTuple(datau._1);
      }
      
      return true;
   }

Un array de tuplas puede ordenarse opcionalmente por el primer campo _1, por lo que puede estudiar adicionalmente el método de ayuda sortTuple.

Con las tuplas puede consultar un objeto de filtro para propiedades de tres tipos diferentes en una sola llamada a select.

A continuación se muestran posiciones con algún número de Magic, ordenadas por beneficio; para cada una se obtiene adicionalmente un símbolo y un ticket.

 input ulong Magic;
   
   void OnStart()
   {
      int props[] = {POSITION_PROFITPOSITION_SYMBOLPOSITION_TICKET};
      Tuple3<double,string,ulongtuples[];
      PositionFilter filter;
      filter.let(POSITION_MAGICMagic).select(propstuplestrue);
      ArrayPrint(tuples);
   }

Por supuesto, los tipos de parámetros de la descripción del array de tuplas (en este caso, Tuple3<double,string,ulong>) deben coincidir con los tipos de enumeración de propiedades solicitados (POSITION_PROFIT, POSITION_SYMBOL, POSITION_TICKET).

Ahora podemos simplificar ligeramente el Asesor Experto de cuadrícula (lo que significa no sólo un código más corto, sino también más comprensible). La nueva versión se llama PendingOrderGrid2.mq5. Los cambios afectarán a todas las funciones relacionadas con la gestión de posiciones.

La función GetMyPositions rellena el array de tuplas types4tickets pasadas por referencia. Se supone que en cada tupla Tuple2 se almacena el tipo y el ticket de la posición. En este caso concreto, podríamos arreglárnoslas simplemente con un array bidimensional ulong en lugar de tuplas porque ambas propiedades son del mismo tipo base. Sin embargo, utilizamos tuplas para demostrar cómo trabajar con ellas en el código de llamada.

#include <MQL5Book/Tuples.mqh>
#include <MQL5Book/PositionFilter.mqh>
   
int GetMyPositions(const string sconst ulong m,
   Tuple2<ulong,ulong> &types4tickets[])
{
   int props[] = {POSITION_TYPEPOSITION_TICKET};
   PositionFilter filter;
   filter.let(POSITION_SYMBOLs).let(POSITION_MAGICm)
      .select(propstypes4ticketstrue);
   return ArraySize(types4tickets);
}

Observe que el último y tercer parámetro del método select es igual a true, que ordena el array por el primer campo, es decir, el tipo de posiciones. Así, tendremos compras al principio y ventas al final. Esto será necesario para el cierre del contador.

La reencarnación del método CompactPositions es la siguiente:

uint CompactPositions(const bool cleanup = false)
{
   uint retcode = 0;
   Tuple2<ulong,ulongtypes4tickets[];
   int i = 0j = 0;
   int n = GetMyPositions(_SymbolMagictypes4tickets);
   if(n > 0)
   {
      Print("CompactPositions: "n);
      for(i = 0j = n - 1i < j; ++i, --j)
      {
         if(types4tickets[i]._1 != types4tickets[j]._1// as long as the types are different
         {
            retcode = CloseByPosition(types4tickets[i]._2types4tickets[j]._2);
            if(retcodereturn retcode// error
         }
         else
         {
            break;
         }
      }
   }
   
   if(cleanup && j < n)
   {
      retcode = CloseAllPositions(types4ticketsij + 1);
   }
   
   return retcode;
}

La función CloseAllPositions es prácticamente la misma:

uint CloseAllPositions(const Tuple2<ulong,ulong> &types4tickets[],
   const int start = 0const int end = 0)
{
   const int n = end == 0 ? ArraySize(types4tickets) : end;
   Print("CloseAllPositions "n - start);
   for(int i = starti < n; ++i)
   {
      MqlTradeRequestSyncLog request;
      request.comment = "close down " + (string)(i + 1 - start)
         + " of " + (string)(n - start);
      const ulong ticket = types4tickets[i]._2;
      if(!(request.close(ticket) && request.completed()))
      {
         Print("Error: position is not closed "ticket);
         return request.result.retcode// error
      }
   }
   return 0// success 
}

Puede comparar el trabajo de los Asesores Expertos PendingOrderGrid1.mq5 y PendingOrderGrid2.mq5 en el probador.

Los informes serán ligeramente diferentes, porque si hay varias posiciones, se cierran en combinaciones opuestas, debido a lo cual el cierre de otras posiciones no emparejadas se produce con respecto a sus diferenciales individuales.