Principios de programación en MQL5: Variables globales del terminal MetaTrader 5

Dmitry Fedoseev | 15 noviembre, 2016


Índice

Introducción

Las variables globales del terminal (Fig. 1) son una particularidad única del terminal MetaTrader y el lenguaje MQL.


Fig. 1. Fragmento del texto con el uso de las variables globales

No hay que confundir las variables globales del terminal con variables globales bien conocidas del programa (Fig. 2) e intentar encontrar para ellas una analogía en otros lenguajes de programación, si los conoce.


Fig. 2. Fragmento del código del experto MovingAverage en los ejemplos del terminal, las variables globales del programa están marcadas con área roja

Tal vez precisamente por la razón de que no hay ninguna analogía a las variables globales del terminal en otros lenguajes de programación, los principiantes en MQL5 no las usan mucho. Puede que simplemente no tienen idea de cómo y para qué se puede usarlas, o su uso les parece muy complicado debido a los nombres de las funciones bastante complejos: GlobalVariableSet(), GlobalVariableGet(), GlobalVariableCheck(), etc.

La principal diferencia de las variables globales del terminal es que guardan sus valores incluso después del cierre del terminal. Precisamente por esa razón ellas son un medio muy cómodo y rápido para guardar los datos importantes, y se convierten en una herramienta casi imprescindible durante el desarrollo de los expertos seguros con complicada interacción entre las órdenes. Después de aprender a trabajar con las variables globales del terminal, le será complicado imaginar la creación de los asesores expertos en MQL5 sin usarlas.

En este artículo usaremos los ejemplos prácticos para estudiar todas las funciones para trabajar con variables globales, analizaremos sus particularidades, estudiaremos diferentes ejemplos de su uso y escribiremos una clase que facilita y acelera considerablemente el trabajo con ellas.

En la documentación de MQL, el apartado con las funciones para trabajar con variables globales se encuentra siguiendo este enlace

Ver las variables globales en el terminal

Ejecute el siguiente comando en el terminal: Menú principal — Herramientas — Variables globales, aparecerá la ventana «Variables globales» (Fig. 3).

 
Fig. 3. Ventana «Variables globales» en el terminal. 

Principalmente, el trabajo con variables globales se ejecuta mediante programación. Pero a veces esta ventana puede ser útil durante la depuración de los expertos. Esta ventana permite no sólo ver todas las variables globales del terminal, sino también editarlas: modificar los nombres o valores. Para modificar una variable, es necesario hacer clic en el campo con su nombre o valor. Además, esta ventana permite crear nuevas variables: para eso pinche en el botón «Añadir» situado en la esquina superior derecha de la ventana. Debajo del botón «Añadir» se encuentra el botón «Eliminar» que permite eliminar las variables globales. Para eliminar una variable, hay que seleccionarla pinchando en ella, después de eso el botón se activa y se puede pulsarlo. Ejercítese en crear, editar y eliminar las variables globales. Pero tenga cuidado: si en la ventana ya existen algunas variables, no las toque porque pueden ser necesarias para algún experto que tiene iniciado.

Una variable global tienen tres atributos: nombre, valor y hora. En el campo «Hora» se muestra la hora de la última llamada a la variable. Transcurridas 4 semanas tras la última llamada, la variable se elimina automáticamente. Es una particularidad muy conveniente, ya que no tiene que preocuparse de su eliminación. No obstante, si las variables guardan datos importantes, hay que llamarlas de vez en cuando para prolongar el plazo de su existencia. 

Crear la variable global del terminal

No hace falta ejecutar ningunas acciones especiales para crear una variable global, se crea automáticamente cuando se le asigna un valor. Si la variable con este nombre ya existía, su valor será actualizado, si no existía, será creada. Para asignar el valor a una variable, se utiliza la función GlobalVariableSet(). La función recibe dos parámetros: nombre de la variable (Sring) y su valor (Double). Ahora intentaremos crear una variable. Abra MetaEditor, crea el script, escriba el siguiente código en su función OnStart():

GlobalVariableSet("test",1.23);

 Ejecute el script, luego abra la ventana de las variables globales desde el terminal, debe contener la variable nueva con el nombre "test" y valor 1,23 (Fig. 4).

 
Fig. 4. Fragmento de la ventana de las variables globales con la variable nueva "test"

Puede encontrar el código de este ejemplo en el script adjunto con el nombre "sGVTestCreate" al final del artículo.  

Obtener el valor de la variable global

Al ejecutar el ejemplo anterior, el script que crea la variable termina su trabajo pero la variable sigue existiendo. Vamos a ver su valor. Para obtener el valor, se utiliza la función GlobalVariableGet(). Hay dos opciones para llamar a la función. Durante la primera opción, la función recibe un parámetro, nombre. La función devuelve el valor tipo double:

double val=GlobalVariableGet("test");
Alert(val);

Al ejecutar este código, se abre la ventana con el valor 1,23. Este ejemplo se encuentra en el script adjunto "sGVTestGet1".

La segunda opción supone que la función recibe dos parámetros: nombre y la variable double para el valor (el segundo parámetro se pasa por referencia), la función devuelve true o false dependiendo del éxito de su trabajo:

double val;
bool result=GlobalVariableGet("test",val);
Alert(result," ",val);

Como resultado, se abre la ventana con el mensaje "true 1.23".

Si intentamos obtener el valor de una variable inexistente, la función devolverá false y el valor 0. Vamos a modificar un poco el código del ejemplo anterior: asignaremos el valor 1,0 a la variable val durante la declaración e intentaremos obtener el valor de la variable inexistente "test2":

double val=1.0;
bool result=GlobalVariableGet("test2",val);
Alert(result," ",val);

Como resultado, se abre la ventana con el mensaje "false 0.0". Este ejemplo se encuentra en el script adjunto "sGVTestGet2-2".

Al llamar a la función usando la primera opción, si ponemos el nombre de una variable inexistente, también obtenemos el valor 0,0 pero además obtenemos el error. Si usamos la primera opción de la llamada a la función y la verificación del error, se puede obtener el análogo de la segunda versión de la función:

ResetLastError();
double val=GlobalVariableGet("test2");
int err=GetLastError();
Alert(val," ",err);

Como resultado de trabajo de este código (script "sGVTestGet1-2"), aparece el mensaje "0.0 4501". 0.0 — valor, 4501 — código del error — "Variable global del terminal de cliente no encontrada". No es un error crítico, más bien informativo. Si el algoritmo lo permite, se puede dirigirse a una variable inexistente. Por ejemplo, para el seguimiento de la equidad máxima:

GlobalVariableSet("max_equity",MathMax(GlobalVariableGet("max_equity",AccountInfoDouble(ACCOUNT_EQUITY)));

Este código va a trabajar correctamente, incluso si la variable "max_equity" no existe. Primero, la función MathMax() realiza la selección del valor máximo entre el valor real de la equidad y el valor guardado anteriormente en la variable "max_equity". Puesto que la variable no existe, obtenemos el valor real de la equidad.

Nombres de las variables globales

Como ya hemos podido comprender, el nombre de la variable global del terminal es una cadena. Para los nombres no existe ninguna limitación en cuanto a los caracteres y su orden. Se puede utilizar cualquier caracter que podemos teclear en el teclado, inclusive los espacios y los caracteres prohibidos en los nombres de los archivos. Sin embargo, se recomienda eligir los nombres simples y legibles que incluyen los números, letras y signo de subrayo como en las variables habituales.

Hay sólo una limitación considerable que requiere estar atento a la hora de eligir el nombre para las variables globales, se trata de la longitud del nombre: no más de 63 caracteres.

Comprobar la existencia de la variable

Para comprobar si la variable existe, se usa la función GlobalVariableCheck(). La función recibe un parámetro, nombre de la variable. Si la variable existe, la función devolverá true, de lo contrario false. Vamos a comprobar la existencia de las variables "test" y "test2":

bool check1=GlobalVariableCheck("test");
bool check2=GlobalVariableCheck("test2");
Alert(check1," ",check2);

Este ejemplo se encuentra en el script adjunto "sGVTestCheck". Como resultado del trabajo del script, obtenemos el mensaje : "true false" — la variable "test" existe, "test2" no existe.

En algunas ocasiones, la comprobación de la existencia de la variable es obligatoria, por ejemplo durante el seguimiento de la equidad máxima. Si en el ejemplo arriba mencionado para el seguimiento de la equidad máxima reemplazamos la función MathMax() por MathMin(), no va a funcionar correctamente y la variable siempre va a tener 0.

En este caso vamos a necesitar la comprobación de la existencia de la variable:

if(GlobalVariableCheck("min_equity")){
   GlobalVariableSet("min_equity",MathMin(GlobalVariableGet("min_equity"),AccountInfoDouble(ACCOUNT_EQUITY)));
}
else{
   GlobalVariableSet("min_equity",AccountInfoDouble(ACCOUNT_EQUITY));

Si la variable existe, seleccionamos el valor mínimo usando la función MathMin(). Si no existe, asignamos seguidamente un valor a la equidad.

Tiempo de la variable global

La hora de la variable global que ya hemos visto en la fig. 3 se puede obtener usando la función GlobalVariableTime(). La función recibe un parámetro, nombre de la variable. La función devuelve el valor tipo datetime:

datetime result=GlobalVariableTime("test");
Alert(result);

Puede encontrar el código de este ejemplo en el script adjunto "sGVTestTime".  El valor del atributo de la hora de la variable se cambia sólo durante la llamada a su valor, es decir cuando se utiliza la función GlobalVariableSet() y GlobalVariableGet(). Ninguna otra función puede cambiar el valor de la hora de la variable. Si cambiamos la variable manualmente a través de la ventana «Variables globales», su hora también se cambia (tanto al cambiar su valor, como su nombre).

Querría recordar otra vez: la variable existe durante 4 semanas desde el momento de la última llamada a ella, luego se elimina automáticamente por el terminal. 

Ver todas las variables globales

A veces es necesario encontrar una variable global sin saber su nombre exacto. Tal vez se sabe el inicio del nombre de la variable, pero no se sabe su fin, por ejemplo: "gvar1", "gvar2" etc. Para la búsqueda de estas variables, es necesario repasar todas las variables globales del terminal y comprobar sus nombres. Para eso usaremos las funciones: GlobalVariablesTotal() y GlobalVariableName(). La función GlobalVariablesTotsl() devuelve el número total de las variables globales. La función GlobalVariableName() devuelve el nombre de la variable por su índice, la función recibe el único parámetro tipo int. Primero repasamos todas las variables, mostramos sus nombres y valores en la ventana informativa:

   Alert("=== Inicio ===");
   int total=GlobalVariablesTotal();
   for(int i=0;i<total;i++){
      Alert(GlobalVariableName(i)," = ",GlobalVariableGet(GlobalVariableName(i)));
   }

Como resultado se abre la ventana con los nombres y valores de todas las variables (Fig.5). Este ejemplo se encuentra en el script adjunto "sGVTestAllNames".

 
Fig. 5. Ventana informativa con la lista de todas las variables globales del terminal

Para ver las variables con determinados signos en los nombres, añadimos la comprobación adicional. En el siguiente ejemplo se realiza la comprobación de que el nombre se empiece con "gvar", (este ejemplo se encuentra en el script "sGVTestAllNames2"):

   Alert("=== Inicio ===");
   int total=GlobalVariablesTotal();
   for(int i=0;i<total;i++){
      if(StringFind(GlobalVariableName(i),"gvar",0)==0){
         Alert(GlobalVariableName(i)," = ",GlobalVariableGet(GlobalVariableName(i)));
      }
   }

La comprobación se realiza a través de la función StringFind(). Para mejorar sus hábitos durante el trabajo con las funciones string, preste su atención en el artículo Principios de programación en MQL5: cadenas.

Eliminar las variables globales

Para eliminar una variable global, se utiliza la función GlobalVariableDel() que recibe sólo un parámetro, nombre de la variable. Eliminamos la variable "test" creada anteriormente (script "sGVTestDelete" en los archivos adjuntos):

GlobalVariableDel("test");

Para comprobar los resultados de este ejemplo, podemos usar el script "sGVTestGet2-1" o "sGVTestGet2-2", o bien se puede abrir la ventana de variables globales.

Con la eliminación de una sola variable está todo claro, pero a menudo es necesario eliminar no una sino varias variables. Para eso conviene la función GlobalVariablesDeleteAll(). La función recibe dos parámetros no obligatorios. Si llamamos a la función sin parámetros, se eliminarán toas las variables globales. Habitualmente surge la necesidad de eliminar un determinado grupo de variables que tienen el mismo prefijo (inicio del nombre). El primer parámetro de la función se utiliza para especificar el prefijo. Vamos a experimentar con esta función. Primero, crearemos varias variables con diferentes prefijos:

   GlobalVariableSet("gr1_var1",1.2);
   GlobalVariableSet("gr1_var2",3.4);   
   GlobalVariableSet("gr2_var1",5.6);
   GlobalVariableSet("gr2_var2",7.8);  

Este código crea cuatro variables: dos con el prefijo "gr1_" y dos con el prefijo "_gr2". Este ejemplo se encuentra en el script adjunto "sGVTestCreate4". Vamos a asegurarnos del trabajo de este script iniciando el script "sGVTestAllNames" (Fig. 6).

 
Fig. 6. Variables creadas con el script "sGVTestCreate4"

Ahora vamos a eliminar las variables que empiezan con el prefijo "gr1_" (script "sGVTestDeleteGroup" en archivos adjuntos):

GlobalVariablesDeleteAll("gr1_");

Después de ejecutar este código, veremos otra vez todas las variables usando el script "sGVTestAllNames" (Fig. 7). Veremos la lista de todas las variables además de las que empiezan con "gr1_".

 
Fig. 7. Grupo de variables con el prefijo "gr1_" ha sido eliminado

El segundo parámetro de la función GlobalVariableDeleteAll() se utiliza si hay que eliminar solamente las variables obsoletas. En este parámetro se especifica la fecha.  Si la fecha de la última llamada a la variable es más antigua que la especificada, la variable será eliminada. Preste atención: se eliminan las variables cuya hora es menor, en vez de menor o igual. Además, se puede seleccionar adicionalmente las variables según el prefijo. Si no necesitamos indicar el prefijo, se pone el valor predefinido NULL para el primer parámetro:

GlobalVariablesDeleteAll(NULL,StringToTime("2016.10.01 12:37"));

Prácticamente, la eliminación de las variables según la hora puede ser necesaria sólo para una tarea rara y extraordinaria, por eso vamos a limitarnos con el análisis teórico del segundo ejemplo.

Función GlobalVariablesFlush

Al cerrar el terminal, las variables globales se guardan automáticamente en el archivo del que el termina las carga durante el siguiente inicio. No es necesario conocer los detalles de este proceso (nombre del archivo, formato del guardado de datos, etc.) a la hora de usar las variables globales.

Las variables globales pueden perderse en caso del cierre de emergencia del terminal. Para evitar este problema, existe la función GlobalVariableFlush(). La función realiza el guardado forzado de variables globales. Después de establecer los valores con la función GlobalVariableSet() o después de eliminar las variables, bastará con llamar a la función GlobalVariableFlush(), la función se llama sin parámetros: 

   GlobalVariableSet("gr1_var1",1.2);
   GlobalVariableSet("gr1_var2",3.4);   
   GlobalVariableSet("gr2_var1",5.6);
   GlobalVariableSet("gr2_var2",7.8);   
   
   GlobalVariablesFlush();

Este código se encuentra en el script adjunto "sGVTestFlush". 

Me gustaría demostrar el efecto del trabajo de la función GlobalVariableFlush(). Sin embargo, durante los experimentos con el cierre de emergencia del terminal no se ha conseguido que las variables globales desaparezcan. El terminal se cerraba a través del Administrador de tareas, la pestaña «Procesos». Puede que las variables globales desaparezcan al cortar bruscamente la corriente del ordenador. Actualmente, el corte de la energía del ordenador es un hecho muy raro, ya que la mayoría de los usuarios tienen los ordenadores portátiles, en los ordenadores de sobremesa se utilizan los sistemas de alimentación interrumpida (UPS en inglés), y si el terminal trabaja en un servidor especialmente asignado, ahí más aun se toman todas las medidas necesarias para asegurar la alimentación interrumpida. Por eso incluso sin la función GlobalVariableFlush(), las variables globales es un medio bastante seguro para guardar los datos.    

Variable temporal, función GlobalVariableTemp

La función GlobalVariableTemp() se encarga de la creación de una variable global temporal (que va a existir sólo hasta el fin del trabajo del terminal). Durante varios años del desarrollo de los expertos en MQL5, el autor de este artículo nunca tuvo la necesidad de usar este tipo de variable. Es más, el mismo fenómeno de la variable global temporal contradice al principio principal por el cual ellas se utilizan: para el almacenamiento prolongado de datos independientemente del reinicio del terminal. Pero dado que esta función está presente en el lenguaje MQL5, vamos a prestarle un poco de atención por si alguien la necesite.

Cuando se invoca la función, ella recibe un parámetro, nombre de la variable. Si la variable con este nombre no existe, será creada la variable temporal con el valor 0. Después de eso, hay que asignarle un valor usando la función GlobalVariableSet(), y continuación usarla como siempre. Si la variable ya existía (fue creada antes por la función GlobalVariableSet()), no va a convertirse en la variable temporal:

   GlobalVariableSet("test",1.2); // establecimiento del valor para la variable para asegurarse de que esta variable existe
   GlobalVariableTemp("temp"); // creación de la variable temporal
   Alert("Valor de la variable temp inmediatamente después de la creación - ",GlobalVariableGet("temp"));
   GlobalVariableSet("temp",3.4); // establecimiento del valor de la variable temporal
   GlobalVariableTemp("test"); // intento de convertir la variable "test" en la temporal

Este ejemplo se encuentra en el script adjunto "sGVTestTemp". Después de iniciar el script, abra la ventana de variables globales, debe contener la variable "temp" con el valor 3.4 y "test" con el valor 1.2. Cierra la ventana de variables globales, reinicie el terminal y vuelva a abrir la ventana. La variable "test" se guardará, y la variable "temp" desaparecerá.

Cambiar la variable según la condición, función GlobalVariableSetOnCondition

Nos queda analizar la última función, según mi opinión es la más interesante: GlobalVariableSetOnCondition(). Esta función recibe tres parámetros: nombre, nuevo valor y condición de control. Si la variable tiene el valor igual al valor de control, se le asigna un valor nuevo y la función devuelve true, de lo contrario devuelve false (además, false se devuelve si la variable no existe).

Según el principio de su funcionamiento, la función es idéntica al siguiente código:

   double check_value=1;
   double value=2;

   if(GlobalVariableGet("test")==check_value){
      GlobalVariableSet("test",value);
      return(true);
   }
   else {
      return(false);
   }

Si el valor de la variable global "test" es igual al valor check_value, se le establece el valor value y se devuelve el valor true, de lo contrario se devuelve el valor false. La variable check_value tiene el valor predefinido 1, para que en caso de la ausencia de la variable "test" la construcción devolvería el valor false.

La finalidad principal de la función GlobalVariableSetOnCondition() consiste en asegurar la ejecución de varios expertos. Puesto que el sistema operativo moderno es de multi tareas, y cada experto representa un flujo separado, no hay seguridad de que un experto ejecute de golpe todo su trabajo desde el principio hasta el final, luego lo mismo hará el segundo experto, etc.

El que antes manejaba el terminal MetaTrader 4, puede recordar el fenómeno como flujo comercial. Ahora varios expertos pueden enviar simultáneamente las órdenes comerciales al servidor, antes sólo un experto podía enviar la orden en un momento dado. Si en el terminal trabajaban varios expertos, al intentar realizar las operaciones comerciales, a menudo surgía el error de ocupación del flujo comercial. Al abrir y cerrar las órdenes, este error no causaba muchas molestias. Un experto escrito correctamente repetirá el intento de la apertura y cierre de la posición, además la coincidencia de los momentos de la apertura y cierre en diferentes expertos no es un caso frecuente. Si la función del Trailing stop (se tiene en cuenta la función en el experto, y no la función estándar del terminal) se activaba en varios expertos, en un tiket sólo de un experto se conseguía ejecutar la modificación del Stop Loss, y eso ya era un problema. A pesar de la ausencia de este problema actualmente, igualmente pueden aparecer las tareas que requieren la ejecución consecutiva de algunos expertos. 

Para asegurar el trabajo consecutivo de un grupo de expertos, utilizamos la variable global en cuestión. En el principio de la ejecución de la función OnTick() vamos a establecer al valor value para la variable. Después de comprobar el valor de la variable y ver el valor value en ella, otros expertos comprenderán que algún experto ya está trabajando y detendrán la ejecución de sus funciones OnTick() o entrarán en el ciclo de espera. Después de terminar todas las acciones del experto, vamos a establecer el valor check_value para la variable. Ahora otro experto podrá iniciar la ejecución de su función OnTick(), etc.

En principio, el código arriba mencionado conviene para la solución de esta tarea, pero no da seguridad de que después de la ejecución de la línea:

if(GlobalVariableGet("test")==check_value){

va a ejecutarse en seguida la línea: 

GlobalVariableSet("test",value);

Es muy probable que entre ellas «se meta» otro experto, «vea» que la variable tiene el valor check_value, empiece su trabajo, ejecute una parte, pero en este momento el primer experto continúe su trabajo. De esta manera, prácticamente dos expertos van a trabajar en paralelo. Pues, para solucionar precisamente este problema, se necesita la función GlobalVariableSetOnCondition(). Como se indica en el Manual de referencia, "la función proporciona el acceso atómico a una variable global". Atómico significa «indivisible». Eso significa que entre la comprobación del valor de la variable y el establecimiento del nuevo valor para ella no se meterá ningún otro programa sí o sí.

El inconveniente a la hora de usar esta función consiste en que ella no crea por sí misma la variable si ésta no existe. Entonces, hay que realizar una comprobación adicional (es deseable hacerlo al inicializar el experto) y crear la variable. 

Escribiremos dos expertos para nuestra prueba. Ambos expertos son absolutamente idénticos, al principio de la función OnTick() se abre la ventana con el mensaje "Inicio de EA1", se hace una pausa de tres segundos (a través de la función Sleep()), al final se muestra el mensaje "Fin de EA1":

void OnTick(){
   Alert("Inicio de EA1");   
   Sleep(3000);   
   Alert("Fin de EA1");
}

El segundo experto es igual, lo único que los mensajes van a ser diferentes: "Inicio de EA2" y "Fin de EA2". En los archivos adjuntos estos expertos tienen los siguientes nombres: "eGVTestEA1" y "eGVTestEA2". Abra dos gráficos iguales en el terminal y adjunte dos expertos a ellos. En la ventana informativa vemos que los expertos empiezan y terminan el trabajo al mismo tiempo, es decir trabajan en paralelo (Fig. 8).


Fig. 8. Mensajes de los expertos sobre el comienzo y el fin de la ejecución de la función OnTick()

Ahora aplicaremos la función GlobalVariableSetOnCondition() para garantizar el trabajo consecutivo de los expertos. El retoque va a ser absolutamente idéntico para ambos expertos, por eso vamos a escribir el código en un archivo incluido. En el anexo, este archivo tendrá el nombre "GVTestMutex.mqh".

Vamos a ver las funciones del archivo "GVTestMutex.mqh". Al inicializar el experto, hay que comprobar la existencia de la variable global, crearla si es necesario (función Mutex_Init()), la función va a recibir el único parámetro, nombre de la variable:

void Init(string name){
   if(!GlobalVariableCheck(name)){
      GlobalVariableSet(name,0);
   }
}

La segunda función es la del control: Mutex_Check(). En esta función va a ejecutarse el ciclo con la espera de la liberación de la variable global. En cuanto la variable quede libre, la función devolverá true y el experto continuará la ejecución de su función OnTick(). Si no se consigue esperar la liberación de la variable, la función devolverá false, y habrá que interrumpir la ejecución de la función OnTick():

bool Mutex_Check(string name,int timeout){   
   datetime end_time=TimeLocal()+timeout; // hora del fin de la espera
   while(TimeLocal()<end_time){ // ciclo durante el tiempo establecido
      if(IsStopped()){
         return(false); // si el experto se quita del gráfico
      }
      if(GlobalVariableSetOnCondition(name,1,0)){ 
         return(true);
      }
      Sleep(1); // pequeña pausa
   }   
   return(false); // no se ha conseguido esperar la liberación
}

La función recibe el nombre de la variable global y el tiempo de espera en segundos.

La tercera función es Mutex_Release(). En ella se establece el valor 0 para la variable global, es decir «desenganche», para que otros expertos puedan empezar su trabajo:

void Mutex_Release(string name){
   GlobalVariableSet(name,0);
}

Hagamos la copia de un experto, incluyamos el archivo en él, añadiéndole la llamada a las funciones. La variable tendrá el nombre "mutex_test", vamos a llamar a la función Mutex_Check() con el tiempo de espera (time-out) de 30 segundos. A continuación se muestra el código completo del experto:

#include <GVTestMutex.mqh>

int OnInit(){
   Mutex_Init("mutex_test");
   return(INIT_SUCCEEDED);
}

void OnTick(){

   if(!Mutex_Check("mutex_test",30)){
      return;
   }

   Alert("Inicio de EA1");
   Sleep(3000);
   Alert("Fin de EA1");

   Mutex_Release("mutex_test");

}

Hagamos la copia de este experto y editemos el texto de mensajes a mostrar. En los archivos adjuntos estos expertos tienen los siguientes nombres: "eGVTestEA1-2" y "eGVTestEA2-2". Vamos a iniciar estos expertos en dos gráficos similares, vemos que ahora trabajan turnándose (Fig. 9).

 
Fig. 9. Expertos trabajan turnándose

Preste atención en el parámetro Time-out: hay que establecer el tiempo que debe ser notoriamente mucho mayor que el tiempo de trabajo de todos los expertos en el grupo. Puede suceder que algún experto se elimine del gráfico en el proceso de ejecución de la función OnTick(), pero la función Mutex_Release() no se ha ejecutado. En este caso, ya ningún experto esperará a su turno. Por eso, para el caso de expiración del Time-out hay que establecer el valor 0 para la variable global, o bien usar otros medios para controlar esta situación. Eso depende de una determinada tarea: si es admisible a veces la ejecución paralela de los expertos, o se requiere sólo su trabajo consecutivo.

Clase para un trabajo cómodo con las variables globales

Para garantizar un trabajo cómodo con las variables globales, tenemos que prestar atención a los siguientes momentos.

  1. Hacen falta los nombres únicos de las variables para cada instancia del experto.
  2. Es necesario asegurar la diferencia de los nombres de las variables durante el trabajo en el Probador de estrategias de los nombres de las variables durante el trabajo en la cuenta.
  3. Si el experto trabaja en el Probador de estrategias, después de terminar cada prueba, este experto debe eliminar todas las variables que ha creado en el proceso de esta prueba.
  4. Asegurar la llamada más cómoda a las funciones relacionadas con variables globales, para que los nombres de las funciones sean más cortos.
Cuando las variables globales se utilizan dentro de un experto, condicionalmente se puede destacar dos tipos de variables: comunes y vinculadas a las órdenes. Las variables comunes se utilizan para almacenar alguna información general relacionada con el trabajo del experto, por ejemplo: hora de inicio de algún evento, beneficio máximo de un grupo de posiciones, etc. Las variables vinculadas a las órdenes contienen la información adicional relacionada a una sola orden (o posición), por ejemplo: índice de la orden en la serie de progresión de lotes, precio de apertura durante la solicitud, etc. Cada orden tiene su número único (ticket), por eso será suficiente formar el nombre a base del ticket de la orden y la parte que determina la denominación de datos guardados en la variable (por ejemplo, "index", "price"). El ticket de la orden es una variable tipo ulong. Su longitud máxima es de 20 caracteres. Una variable tiene 63 caracteres de longitud máxima, así que nos queda a nuestra disposición otros 43 caracteres.

La formación de los nombre para las variables comunes supone una tarea más complicada. Hagamos una valoración aproximada de una posible longitud de la variable. El primer indicio que permite separar las variables de un experto del otro es el nombre del experto, supongamos que tenga de 20 caracteres. Los mismos expertos pueden trabajar con símbolos diferentes. Eso significa que el segundo indicio único es el símbolo (4 caracteres más). Los expertos que trabajan en un símbolo se diferencian con los identificadores de las órdenes, llamados números mágicos. Tienen el tipo ulong (longitud máxima es de 20 caracteres). Desde el mismo terminal se puede cambiar entre las cuentas, el numero de la cuenta es una variable tipo long (longitud máxima es de 19 caracteres) Pues bien, en total tenemos 63 caracteres. Pero según las condiciones establecidas, es la longitud permitida para una variable. ¡Y eso es solamente la longitud del prefijo!

Entonces, tenemos que sacrificar algo. Vamos a seguir la siguiente regla: un terminal trabaja con una sola cuenta. Si tiene varias cuentas, instale su versión del terminal para cada una de ellas. Así, podemos quitar del medio el número de la cuenta, y el tamaño máximo del prefijo se reducirá a 43 caracteres, obtenemos 20 caracteres libres. Se puede seguir una regla más: no usar los números mágicos largos. Finalmente, no estará de más prestar atención a los nombres de los expertos, darles los nombres más cortos. Se puede considerar admisible el nombre de una variable global compuesto del nombre del experto, símbolo y el número mágico. Es probable que alguien pueda encontrar una manera más conveniente para formar los nombres, pero en este artículo vamos a usar este modo.

Empecemos a escribir la clase. La clase va a llamarse "CGlobalVariables", el nombre del archivo será "CGlobalVariables.mqh" en el anexo. Declaramos dos variables para los prefijos en la sección private: una para las variables comunes y la otra para las que están vinculadas a las órdenes.

class CGlobalVariables{
   private:
      string m_common_prefix; // prefijo para las variables comunes
      string m_order_prefix; // prefijo para las variables de las órdenes
   public:
      // constructor
      void CGlobalVariables(){}
      // destructor
      void ~CGlobalVariables(){}
}

Creamos el método Init() en la sección public. Este método va a ser llamado cuando se inicializa el experto, recibiendo dos parámetros:símbolo y número mágico. En este método van a formarse los prefijos. Los prefijos de las variables de las órdenes son sencillos: sólo hay que separar las variables del experto que trabaja en la cuenta del experto que trabaja en el Probador de estrategias. Entonces, las variables de las órdenes en la cuenta van a empezarse con el prefijo "order_", en el Probador de estrategias, con el prefijo "tester_order_". Al prefijo de las variables comunes en el Probador sólo le añadimos "t_" (ya son únicas por si mismas, además hay que ahorrar la cantidad de caracteres). Durante la inicialización en el Probador hay que eliminar las variables globales viejas. Desde luego hay que eliminarlas también durante la deinicialización, pero no se sabe cómo se ha terminado la prueba: tal vez se ha quedado alguna variable. Por ahora simplemente creamos el método DeleteAll() y lo llamamos. Es mejor colocar este método en la sección private, escribiremos el código para él más tarde. A continuación se muestra el código del método Init():

void Init(string symbol,int magic){
   m_order_prefix="order_";
   m_common_prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+symbol+"_"+IntegerToString(magic)+"_";
   if(MQLInfoInteger(MQL_TESTER)){
      m_order_prefix="tester_"+m_order_prefix;
      m_common_prefix="t_"+m_common_prefix;
      DeleteAll();
   }         
}

Seguidamente añadimos el método que devuelve el prefijo de las variables comunes, tal vez sea necesario para algún trabajo especial con las variables globales:

string Prefix(){
   return(m_common_prefix);
} 

Añadimos los métodos principales: para comprobar, establecer, obtener el valor y eliminar determinadas variables. Puesto que hay dos prefijos, para cada función habrá dos métodos con el reinicio de los nombres (dos funciones con los mismos nombres pero con el conjunto diferente de parámetros). Un grupo de métodos va a recibir sólo un parámetro, nombre de la variable. Son métodos para las variables comunes. Otro grupo de funciones va a recibir el ticket y el nombre de la variable, son métodos para las variables de las órdenes:

//--- para variables comunes
bool Check(string name){
   return(GlobalVariableCheck(m_common_prefix+name));
}
void Set(string name,double value){
   GlobalVariableSet(m_common_prefix+name,value);      
}      
double Get(string name){
   return(GlobalVariableGet(m_common_prefix+name));
} 
void Delete(string name){
   GlobalVariableDel(m_common_prefix+name); 
}
//--- para variables comunes de las órdenes
bool Check(ulong ticket,string name){
   return(GlobalVariableCheck(m_order_prefix+IntegerToString(ticket)+"_"+name));
}
void Set(ulong ticket,string name,double value){
   GlobalVariableSet(m_order_prefix+IntegerToString(ticket)+"_"+name,value);      
}      
double Get(ulong ticket,string name){
   return(GlobalVariableGet(m_order_prefix+IntegerToString(ticket)+"_"+name));
} 
void Delete(ulong ticket,string name){
   GlobalVariableDel(m_order_prefix+IntegerToString(ticket)+"_"+name); 
} 

Volvemos al método DeleteAll(), escribiremos el código para la eliminación de variables según los prefijos:

GlobalVariablesDeleteAll(m_common_prefix);
GlobalVariablesDeleteAll(m_order_prefix);  

Esta eliminación debe realizarse en el Probador de estrategias al terminar la prueba , por eso añadiremos el método Deinit() que va a invocarse durante la deinicialización del experto:

 void Deinit(){
    if(MQLInfoInteger(MQL_TESTER)){
        DeleteByPrefix();
    }
 }

Para aumentar la seguridad de las variables globales, es necesario usar la función GlobalVariablesFlush(), por eso añadiremos un método más con esta función. Es mucho más fácil llamar al método de la clase que escribir el nombre largo de la función (ejecución del requerimiento del punto 4):

void Flush(){
   GlobalVariablesFlush();
}

A veces puede surgir la necesidad de juntar las variables comunes en grupos a través de añadirles los prefijos adicionales, y eliminar estos grupos en el proceso de trabajo del experto. Añadimos el método DeletByPrefix():

void DeleteByPrefix(string prefix){
   GlobalVariablesDeleteAll(m_common_prefix+prefix);
}

Como resultado, hemos obtenido el mínimo suficiente de la funcionalidad de la clase, pero el que permite solucionar de hasta un 95% de tareas relacionadas con las variables globales.

Para usar la clase, hay que incluir el archivo al experto:

#include <CGlobalVariables.mqh>

Crear el objeto:

CGlobalVariables gv;

Llamar al método Init() durante la inicialización del experto, pasándole el símbolo y el número mágico:

gv.Init(Symbol(),123);

Llamar al método Deinit() durante la deinicialización para eliminar las variables desde el Probador:

gv.Deinit();

Después de eso, en el proceso del desarrollo del experto sólo nos queda usar los métodos Check(), Set(), Get(), Delete() pasándoles solamente la parte única del nombre de la variable, por ejemplo:

   gv.Set("name1",123.456);
   double val=gv.Get("name1");

Como resultado de trabajo de este experto, en la lista de las variables globales del terminal se puede ver la variable con el nombre "eGVTestClass_GBPJPY_123_name1", Fig. 10.

 
Fig. 10 Fragmento de la ventana de las variables globales con la variable creada a través de la clase CGlobalVariables

La longitud de la variable es de 29 caracteres. Hay un margen bastante grande y durante el uso de esta clase podemos sentirnos bastante libres al usar los nombres para las variables. Para las variables de las órdenes, además es necesario enviar el ticket de la orden pero ya sin la necesidad de formar cada vez el nombre completo, y llamar a la función IntegerToSTring() para la transformación del ticket en la cadena, lo que facilita considerablemente el uso de las variables globales. El ejemplo del uso de la clase se encuentra en el anexo en el experto con el nombre "eGVTestClass".

Se puede modificar un poco esta clase para que su uso sea aún más cómodo. Retocaremos el constructor y el destructor de la clase. Añadimos la llamada al método Init() y los parámetros al constructor, añadimos la llamada al método Deinit() al destructor:

void CGlobalVariables(string symbol="",int magic=0){
   Init(symbol,magic);
}
// destructor
void ~CGlobalVariables(){
   Deinit();
}

Después de eso ya no hace falta llamar a los métodos Init() y Deinit(), basta con indicar el número mágico al crear la instancia de la clase:

CGlobalVariables gv(Symbol(),123);

Conclusión

En este artículo hemos considerado detalladamente todas las funciones para el trabajo con las variables globales del terminal, además hemos analizado detalladamente la función GlobalVariableSetOnCondition(). Hemos creado la clase que facilita considerablemente el uso de las variables globales durante la creación de los expertos. Desde luego, la clase incluye no todas las posibilidades para el trabajo con las variables globales, sino las más cruciales y del uso más frecuente. Si esta interesado en el enfoque al trabajo con variables globales que se considera en este artículo, Usted puede modificar la clase propuesta o crear la suya. 

Archivos adjuntos

  • sGVTestCreate — creación de la variable temporal.
  • sGVTestGet1 — la primera opción para obtener el valor.
  • sGVTestGet2  — la segunda opción para obtener el valor.
  • sGVTestGet1-2 — la primera opción para obtener el valor de una variable inexistente.
  • sGVTestGet2-2 — la segunda opción para obtener el valor de una variable inexistente.
  • sGVTestCheck — comprobación de la existencia de la variable.
  • sGVTestTime — obtener la hora de la variable.
  • sGVTestAllNames — obtener la lista de los nombres de todas las variables.
  • sGVTestAllNames2 — obtener la lista de los nombres con prefijo establecido.
  • sGVTestDelete — eliminación de la variable.
  • sGVTestCreate4 — creación de cuatro variables (dos grupos por dos variables).
  • sGVTestDeleteGroup — eliminación de un grupo de variables.
  • sGVTestFlush — guardado forzado de variables.
  • sGVTestTemp — creación de una variable temporal.
  • eGVTestEA1, eGVTestEA2 — demostración del trabajo de variables en paralelo.
  • GVTestMutex.mqh — funciones para crear el mutex.
  • eGVTestEA1-2, eGVTestEA1-2 — demostración del trabajo consecutivode variables.
  • CGlobalVariables.mqh — clase CGlobalVariables para el trabajo con variables globales.
  • eGVTestClass — experto con el ejemplo del uso de la clase CGlobalVariables.