Obtener una lista general de las propiedades del terminal y del programa

Las funciones integradas disponibles para obtener propiedades de entorno utilizan un enfoque genérico: las propiedades de cada tipo específico se combinan en una función independiente con un único argumento que especifica la propiedad solicitada. Hay enumeraciones definidas para identificar propiedades: cada elemento describe una propiedad.

Como veremos más adelante, este enfoque se utiliza a menudo en la API de MQL5 y en otras áreas, incluidas las áreas de aplicación. En concreto, se utilizan conjuntos de funciones similares para obtener las propiedades de cuentas de trading e instrumentos financieros.

Las propiedades de tres tipos simples, int, double y string, son suficientes para describir el entorno. No obstante, no sólo se presentan propiedades de enteros utilizando valores del tipo int, sino también banderas lógicas (en particular, permisos/prohibiciones, presencia de una conexión de red, etc.), así como otras enumeraciones integradas (por ejemplo, tipos de programas MQL y tipos de licencias).

Dada la división condicional en propiedades de terminales y propiedades de un programa MQL concreto, existen las siguientes funciones que describen el entorno.

int MQLInfoInteger(ENUM_MQL_INFO_INTEGER p)

int TerminalInfoInteger(ENUM_TERMINAL_INFO_INTEGER p)

double TerminalInfoDouble(ENUM_TERMINAL_INFO_DOUBLE p)

string MQLInfoString(ENUM_MQL_INFO_STRING p)

string TerminalInfoString(ENUM_TERMINAL_INFO_STRING p)

Estos prototipos asignan tipos de valores a tipos de enum. Por ejemplo, las propiedades del terminal del tipo int se resumen en ENUM_TERMINAL_INFO_INTEGER, y sus propiedades del tipo double se enumeran en ENUM_TERMINAL_INFO_DOUBLE, etc. La lista de enums disponibles y sus elementos puede consultarse en la documentación, en las secciones sobre Propiedades de los terminales y Programas MQL.

En las siguientes secciones veremos todas las propiedades, agrupadas en función de su finalidad. Pero aquí nos planteamos el problema de obtener una lista general de todas las propiedades existentes y sus valores. Esto suele ser necesario para identificar «cuellos de botella» o características del funcionamiento de los programas MQL en instancias específicas del terminal. Una situación bastante común es cuando un programa MQL funciona en un ordenador, pero no funciona en absoluto o el funcionamiento presenta algunos problemas en otro.

La lista de propiedades se actualiza constantemente a medida que se desarrolla la plataforma, por lo que es aconsejable realizar su solicitud no sobre la base de una lista programada en el código fuente, sino de forma automática.

En la sección Enumeraciones hemos creado una función de plantilla EnumToArray para obtener una lista completa de elementos de enumeración (archivo EnumToArray.mqh). También en esa sección introdujimos el script ConversionEnum.mq5, que utiliza el archivo de encabezado especificado. En el script se implementó una función de ayuda process, que recibía un array con códigos de elementos de enumeración y los mostraba en el registro. Tomaremos estos avances como punto de partida para seguir mejorando.

Tenemos que modificar la función process de tal forma que no sólo obtengamos una lista de los elementos de una enumeración determinada, sino que también consultemos las propiedades correspondientes utilizando una de las funciones de propiedades integradas.

Vamos a dar un nombre a la nueva versión del script, Environment.mq5.

Dado que las propiedades del entorno están dispersas en varias funciones diferentes (en este caso, cinco), es necesario descubrir cómo pasar a la nueva versión de la función process un puntero a la función integrada necesaria (véase la sección Punteros de función (typedef)). Sin embargo, MQL5 no permite asignar la dirección de una función integrada a un puntero de función. Esto sólo puede hacerse con una función de aplicación implementada en MQL5. Por lo tanto, crearemos funciones de envoltorio. Por ejemplo:

int _MQLInfoInteger(const ENUM_MQL_INFO_INTEGER p)
{
   return MQLInfoInteger(p);
}
// example of pointer type description  
typedef int (*IntFuncPtr)(const ENUM_MQL_INFO_INTEGER property);
// initialization of pointer variables
IntFuncPtr ptr1 = _MQLInfoInteger;  // ok
IntFuncPtr ptr2 = MQLInfoInteger;   // compilation error

Arriba se muestra un «double» para MQLInfoInteger (obviamente, debería tener un nombre diferente, pero preferiblemente similar). Otras funciones se «empaquetan» de forma similar. Habrá cinco en total.

Si en la versión antigua de process sólo había un parámetro de plantilla especificando una enumeración; en la nueva necesitamos pasar también el tipo del valor de retorno (ya que MQL5 no «entiende» las palabras en el nombre de las enumeraciones): aunque la terminación «INTEGER» esté presente en el nombre ENUM_MQL_INFO_INTEGER, el compilador no es capaz de asociarlo con el tipo int).

Sin embargo, además de vincular los tipos del valor de retorno y de la enumeración, necesitamos pasar de algún modo a la función process un puntero a la función de envoltorio apropiada (una de las cinco que definimos anteriormente). Al fin y al cabo, el propio compilador no puede determinar mediante un argumento del tipo, por ejemplo, ENUM_MQL_INFO_INTEGER, que es necesario llamar a MQLInfoInteger.

Para resolver este problema se ha creado una estructura de plantilla especial que combina los tres factores.

template<typename Etypename R>
struct Binding
{
public:
   typedef R (*FuncPtr)(const E property);
   const FuncPtr f;
   Binding(FuncPtr p): f(p) { }
};

Los dos parámetros de la plantilla permiten especificar el tipo del puntero de la función (FuncPtr) con la combinación deseada de parámetros de resultado y de entrada. La instancia de la estructura tiene el campo f para un puntero a ese tipo recién definido.

Ahora, una nueva versión de la función process puede describirse del siguiente modo:

template<typename Etypename R>
void process(Binding<ER> &b)
{
   E e = (E)0// turn off the warning about the lack of initialization
   int array[];
   // get a list of enum elements into an array
   int n = EnumToArray(earray0USHORT_MAX);
   Print(typename(E), " Count="n);
   ResetLastError();
   // display the name and value for each element,
   // obtained by calling a pointer in the Binding structure
   for(int i = 0i < n; ++i)
   {
      e = (E)array[i];
      R r = b.f(e); // call the function, then parse _LastError
      const int snapshot = _LastError;
      PrintFormat("% 3d %s=%s"iEnumToString(e), (string)r +
         (snapshot != 0 ? E2S(snapshot) + " (" + (string)snapshot + ")" : ""));
      ResetLastError();
   }
}

El argumento de entrada es la estructura Binding. Contiene un puntero a una función específica para obtener propiedades (este campo será rellenado por el código de llamada).

Esta versión del algoritmo registra el número de secuencia, el identificador de la propiedad y su valor. De nuevo, tenga en cuenta que el primer número de cada entrada contendrá el ordinal del elemento en la enumeración, no el valor (se pueden asignar valores a elementos con huecos). Opcionalmente puede añadir una salida de una variable e «en su forma pura» dentro de las instrucciones print format.

Además, puede modificar el proceso para que recoja en un array (u otro contenedor, como un mapa) los valores de las propiedades resultantes y los devuelva «fuera».

Sería un error en potencia hacer referencia al puntero de función directamente en la instrucción print format junto con el análisis del código de error _LastError. La cuestión es que la secuencia de evaluación de los argumentos de las funciones (véase la sección Parámetros y argumentos) y los operandos de una expresión (véase la sección Conceptos básicos) no está definido en este caso. Por lo tanto, cuando se llama a un puntero en la misma línea en la que se lee _LastError, el compilador puede decidir ejecutar el segundo antes que el primero. Como resultado, veremos un código de error irrelevante (por ejemplo, de una llamada a una función anterior).

Pero eso no es todo. La variable integrada _LastError puede cambiar su valor casi en cualquier punto de la evaluación de una expresión si falla alguna operación. En concreto, la función EnumToString puede potencialmente generar un código de error si se pasa como argumento un valor que no está en la enumeración. En este fragmento, somos inmunes a este problema porque nuestra función EnumToArray devuelve un array sólo con elementos de enumeración comprobados (válidos). No obstante, en casos generales, en cualquier instrucción «compuesta», puede haber muchos lugares en los que _LastError se cambie. A este respecto, es conveniente fijar el código de error inmediatamente después de la acción que nos interesa (aquí se trata de una llamada a una función mediante un puntero), guardándolo en una variable intermedia snapshot.

Pero volvamos a la cuestión principal. Para terminar podemos organizar una llamada a la nueva función process a fin de obtener diversas propiedades del entorno de software.

void OnStart()
{
   process(Binding<ENUM_MQL_INFO_INTEGERint>(_MQLInfoInteger));
   process(Binding<ENUM_TERMINAL_INFO_INTEGERint>(_TerminalInfoInteger));
   process(Binding<ENUM_TERMINAL_INFO_DOUBLEdouble>(_TerminalInfoDouble));
   process(Binding<ENUM_MQL_INFO_STRINGstring>(_MQLInfoString));
   process(Binding<ENUM_TERMINAL_INFO_STRINGstring>(_TerminalInfoString));
}

A continuación se muestra un fragmento de las entradas de registro generadas.

ENUM_MQL_INFO_INTEGER Count=15
  0 MQL_PROGRAM_TYPE=1
  1 MQL_DLLS_ALLOWED=0
  2 MQL_TRADE_ALLOWED=0
  3 MQL_DEBUG=1
...
  7 MQL_LICENSE_TYPE=0
...
ENUM_TERMINAL_INFO_INTEGER Count=50
  0 TERMINAL_BUILD=2988
  1 TERMINAL_CONNECTED=1
  2 TERMINAL_DLLS_ALLOWED=0
  3 TERMINAL_TRADE_ALLOWED=0
...
  6 TERMINAL_MAXBARS=100000
  7 TERMINAL_CODEPAGE=1251
  8 TERMINAL_MEMORY_PHYSICAL=4095
  9 TERMINAL_MEMORY_TOTAL=8190
 10 TERMINAL_MEMORY_AVAILABLE=7813
 11 TERMINAL_MEMORY_USED=377
 12 TERMINAL_X64=1
...
ENUM_TERMINAL_INFO_DOUBLE Count=2
  0 TERMINAL_COMMUNITY_BALANCE=0.0 (MQL5_WRONG_PROPERTY,4512)
  1 TERMINAL_RETRANSMISSION=0.0
ENUM_MQL_INFO_STRING Count=2
  0 MQL_PROGRAM_NAME=Environment
  1 MQL_PROGRAM_PATH=C:\Program Files\MT5East\MQL5\Scripts\MQL5Book\p4\Environment.ex5
ENUM_TERMINAL_INFO_STRING Count=6
  0 TERMINAL_COMPANY=MetaQuotes Software Corp.
  1 TERMINAL_NAME=MetaTrader 5
  2 TERMINAL_PATH=C:\Program Files\MT5East
  3 TERMINAL_DATA_PATH=C:\Program Files\MT5East
  4 TERMINAL_COMMONDATA_PATH=C:\Users\User\AppData\Roaming\MetaQuotes\Terminal\Common
  5 TERMINAL_LANGUAGE=Russian
 

Estas y otras propiedades se describirán en las secciones siguientes.

Cabe señalar que algunas propiedades se heredan de etapas anteriores de desarrollo de la plataforma y se dejan sólo por compatibilidad. En concreto, la propiedad TERMINAL_X64 en TerminalInfoInteger devuelve una indicación de si el terminal es de 64 bits. En la actualidad, el desarrollo de versiones de 32 bits se ha interrumpido, por lo que esta propiedad es siempre igual a 1 (true).