Principios de programación en MQL5 - Variables globales del terminal
Introducción
En el entorno MQL4/5 hay un instrumento interesante, las variables globales del terminal de cliente. Este instrumento permite crear una cierta zona común para el guardado de datos para todos los programas del terminal. Además, la vida de esta zona no se interrumpe al cerrar el terminal. En este artículo, propongo utilizar los medios de la POO, para "aclararnos" con las variables globales del programa.
En el texto del artículo, en caso de que se diga lo contrario, las variables globales del terminal de cliente se llamarán para mayor brevedad "varibales globales".
1. Varibales globales, funciones
Desde el punto de vista del programador, una varibale global es una parte de la memoria con denominación, que está accesible para todos los programas que funcionen en un terminal comercial. Seguramente merezca la pena llamarle la atención a los principiantes sobre el hecho de que si funcionan varios terminales simultáneamente, entonces cada uno de ellos tendrá su espacio independiente en el memoria para las variables globales. Dichos espacios no se cruzarán.
Los desarrolladores del lenguaje, en la documentación indican que hay 11 funciones que procesan las necesidades de las variables globales.
En el manual de MQL4 existe el apartado "Variables GlobalVariables", donde se estudia la teoría.
En los siguientes apartados usaremos los medios de la POO para la ejecución de las tareas marcadas.
2. Clase CGlobalVar
Guiándonos por las ideas de la POO, creamos la clase CGlobalVar, que será directamente responsable del objeto de la variable global.
//+------------------------------------------------------------------+ //| Class CGlobalVar | //+------------------------------------------------------------------+ class CGlobalVar : public CObject { //--- === Data members === --- private: string m_name; double m_value; //--- datetime m_create_time; datetime m_last_time; //--- flag for temporary var bool m_is_temp; //--- === Methods === --- public: //--- constructor/destructor void CGlobalVar(void); void CGlobalVar(const string _var_name,const double _var_val, const datetime _create_time); void ~CGlobalVar(void){}; //--- create/delete bool Create(const string _var_name,const double _var_val=0.0, const bool _is_temp=false); bool Delete(void); //--- exist bool IsGlobalVar(const string _var_name,bool _to_print=false); //--- set methods bool Value(const double _var_val); bool ValueOnCondition(const double _var_new_val,const double _var_check_val); //--- get methods string Name(void) const; datetime CreateTime(void) const; datetime LastTime(void); template<typename T> T GetValue(T _type) const; bool IsTemporary(void) const; //--- private: string FormName(const string _base_name,const bool _is_temp=false); };
¿Qué debe incluir dentro de sí una clase? Para conseguir una lista mínima de atributos, yo elegiría las siguientes propiedades:
- el nombre de la variable;
- el valor de la variable;
- la hora de creación;
- la hora de la última llamada;
- el signo de la variable temporal.
En lo que respecta a los métodos, estos se muestran de la siguiente forma:
- creación;
- eliminación;
- comprobación de la existencia;
- establecimiento del nuevo valor;
- establecimiento del nuevo valor según una condición;
- obtención del nombre;
- obtención del del valor;
- obtención de la etiqueta de la variable temporal.
Merece la pena decir unas palabras sobre el método CGlobalVar::GetValue. Se trata de un método de plantilla. Retornará aquel tipo de datos para el valor de la variable que el usuario establezca en calidad de argumento.
El asunto es que en MQL solo se puede tipificar una función por sus parámetros. Por eso hay que añadir un parámetro falso.
Creamos el script textual Globals_test1.mq5, en el que trabajaremos con los objetos de tipo CGlobalVar.
#include "CGlobalVar.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { CGlobalVar gVar1; //--- create a temporary global var if(gVar1.Create("Gvar1",3.123456789101235,true)) { Print("\n---=== A new global var ===---"); PrintFormat("Name: \"%s\"",gVar1.Name()); PrintFormat("Is temporary: %d",gVar1.IsTemporary()); //--- Get the value //--- double type double d=0.0; double dRes=gVar1.GetValue(d); PrintFormat("Double value: %0.15f",dRes); //--- float type float f=0.0; float fRes=gVar1.GetValue(f); PrintFormat("Float value: %0.7f",fRes); //--- string type string s=NULL; string sRes=gVar1.GetValue(s); PrintFormat("String value: %s",sRes); //--- Set a new value double new_val=3.191; if(gVar1.Value(new_val)) PrintFormat("New value is set: %f",new_val); //--- Set a new value on condition new_val=3.18; if(gVar1.ValueOnCondition(3.18,3.191)) PrintFormat("New value on conditionis set: %f",new_val); } }
Una variable global se crea de la siguiente manera:
gVar1.Create("Gvar1",3.123456789101235,true)
Como primer argumento actúa la parte básica del nombre de la futura variable ("Gvar1"), como segunda, el valor (3.123456789101235), y como tercera el signo que indica que la variable será temporal (true).
El nombre para la variable se crea de esta forma: a la parte básica se añaden el nombre del programa y el tipo de programa.
En mi caso sería:
- Gvar1 es la parte básica;
- prog_Globals_test1 es el programa donde fue creada la variable (de nombre Globals_test1);
- el tipo de programa - scr (script).
En la lista de variables globales, después de pulsar F3 en la ventana del terminal MetaTrader 5, deberá aparecer el siguiente mensaje:
Fig. 1. El valor de la variable Test_temp_var1_prog_Globals_test1_scr es igual a 3.18
Al iniciarlo y ejecutarlo con éxito en el registro "Expertos" aparecerán las siguientes líneas:
KP 0 10:20:20.736 Globals_test1 (AUDUSD.e,H1) ---=== A new global var ===--- EH 0 10:20:21.095 Globals_test1 (AUDUSD.e,H1) Name: "Gvar1_temp_prog_Globals_test1_scr" LF 0 10:20:21.876 Globals_test1 (AUDUSD.e,H1) Is temporary: 1 MO 0 10:20:31.470 Globals_test1 (AUDUSD.e,H1) Double value: 3.123456789101235 KG 0 10:20:31.470 Globals_test1 (AUDUSD.e,H1) Float value: 3.1234567 OP 0 10:20:31.470 Globals_test1 (AUDUSD.e,H1) String value: 3.123456789101235 RH 0 10:20:31.470 Globals_test1 (AUDUSD.e,H1) New value is set: 3.191000 DJ 0 10:20:31.470 Globals_test1 (AUDUSD.e,H1) New value on conditionis set: 3.180000
En el registro se muestran para imprimir los diferentes tipos de datos del valor de la variable.
Si reiniciamos el terminal MetaTrader 5, entones la variable Gvar1_temp_prog_Globals_test1_scr desaparecerá del conjunto de variables globales. Esto sucede en vista de que la variable era una variable, es decir, existió hasta que se abrió el propio terminal.
En MQL4/5, al obtener los datos sobre la variable global no se puede saber si la variable es temporal o no. Seguramente, el método más simple para identificar la variable temporal pueda ser una cierta clave en el nombre de la propia variable. Se puede añadir, por ejemplo, el sufijo, "temp" al nombre de la variable. Un defecto de este enfoque es que sería necesario controlar el nombre de la variable global al crearla. Sobre todo si esas variables son creadas con otros programas que no utilicen la clase CGlobalVar.
Me resultó interesante, ¿cuántas variables se pueden crear, y cómo de rápido?
Cambié un poco la versión anterior del script y lo puse en marcha con un número diferente de pasadas, Globals_test2.mq5. Sí, después de cada intento reiniciaba el terminal, para eliminar las variables.
#property script_show_inputs //--- #include "CGlobalVar.mqh" input uint InpCnt=10000; // Cantidad de variables //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- start value uint start=GetTickCount(); //--- for(uint idx=0;idx<InpCnt;idx++) { CGlobalVar gVar; //--- Create a temporary global var if(!gVar.Create("Test_var"+IntegerToString(idx+1),idx+0.15,true)) Alert("Error al crear la variable!"); } //--- finish value uint time=GetTickCount()-start; //--- to print PrintFormat("La creación %d variables globales ha tardado %d msec",InpCnt,time); }
Esto es lo que ha resultado (fig.2).
Fig.2. Gasto de tiempo en la creación de variables globales temporales
Los resultados de un test análogo para la ejecución de variables globales los podemos ver en la fig.3. En su reación se invierte un poco más de tiempo.
Eso sucede así porque estas variables se guardan en el disco, en el archivo gvariables.dat, que se encuentra en la carpeta Profiles.
Fig.3. Gasto temporal en la creación de variables globales plenamente funcionales
Creo que no es necesario crear tantas variables globales. He realizado los cálculos por curiosidad.
En el siguiente apartado trabajaremos con un conjunto de variables globales.
3. Clase CGlobalVarList
Para que haya orden durante el trabajo con las variables globales, crearemos una clase de lista de las variables globales del tipo CGlobalVarList. Este tipo de lista será heredero de la lista estándar de clase CList.
La determinación de la clase puede mostrarse así:
//+------------------------------------------------------------------+ //| Class CGlobalVarList | //+------------------------------------------------------------------+ class CGlobalVarList : public CList { //--- === Data members === --- private: ENUM_GVARS_TYPE m_gvars_type; //--- === Methods === --- public: //--- constructor/destructor void CGlobalVarList(void); void ~CGlobalVarList(void){}; //--- load/unload bool LoadCurrentGlobals(void); bool KillCurrentGlobals(void); //--- working with files virtual bool Save(const int _file_ha); virtual bool Load(const int _file_ha); //--- service void Print(const int _digs); void SetGvarType(const ENUM_GVARS_TYPE _gvar_type); //--- private: bool CheckGlobalVar(const string _var_name); };
Si hay que incluir en una lista de tipo CGlobalVarList objetos relacionados con la variable global actual, entonces se usa el método CGlobalVarList::LoadCurrentGlobals.
//+------------------------------------------------------------------+ //| Load current global vars | //+------------------------------------------------------------------+ bool CGlobalVarList::LoadCurrentGlobals(void) { ENUM_GVARS_TYPE curr_gvar_type=this.m_gvars_type; int gvars_cnt=GlobalVariablesTotal(); //--- for(int idx=0;idx<gvars_cnt;idx++) { string gvar_name=GlobalVariableName(idx); if(this.CheckGlobalVar(gvar_name)) continue; //--- gvar properties double gvar_val=GlobalVariableGet(gvar_name); datetime gvar_time=GlobalVariableTime(gvar_name); CGlobalVar *ptr_gvar=new CGlobalVar(gvar_name,gvar_val,gvar_time); //--- control gvar type if(CheckPointer(ptr_gvar)==POINTER_DYNAMIC) { if(curr_gvar_type>GVARS_TYPE_ALL) { bool is_temp=ptr_gvar.IsTemporary(); //--- only full-fledged if(curr_gvar_type==GVARS_TYPE_FULL) {if(is_temp)continue;} //--- only temporary else if(curr_gvar_type==GVARS_TYPE_TEMP) {if(!is_temp)continue;} } //--- try to add if(this.Add(ptr_gvar)>-1) continue; } //--- return false; } //--- return true; }
Este método calcula todas las variables globales existentes y las incluye en el conjunto de la lista.
El atributo m_gvars_type controla el aspecto de la variable global incliuda. Se trata de una enumeración del tipo ENUM_GVARS_TYPE:
//+------------------------------------------------------------------+ //| Enumeration for gvars type | //+------------------------------------------------------------------+ enum ENUM_GVARS_TYPE { GVARS_TYPE_ALL=-1, // todas las globales GVARS_TYPE_FULL=0, // solo las plenamente funcionales GVARS_TYPE_TEMP=1, // solo las temporales };
Supongamos que antes de incializar la lista CGlobalVarList tuviéramos esta serie de variables globales (fig.4)
Fig.4. Listado aproximado de variables globales
Veamos si es posible procesar este listado con la lista correctamente. Para realizar la comprobación creamos el script textual Globals_test3.mq5.
#include "CGlobalVarList.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { CGlobalVarList gvarList; gvarList.LoadCurrentGlobals(); PrintFormat("Cantidad de variables en la lista: %d",gvarList.Total()); }
Después de iniciar el script han aparecido nueveas variables globales (las he destacado en amarillo), lo que no se presuponía, en principio (fig.5).
Fig.5. Nuevo listado de variables globales
Y la línea se ha imprimido así:
2014.10.21 11:35:00.839 Globals_test3 (AUDUSD.e,H1) Cantidad de variables en la lista: 10
El asunto es que en la determinación del método CGlobalVarList::LoadCurrentGlobals hay una llamada al método CGlobalVar::Create.
Es decir, se crea una nueva variable global en la línea:
if(ptr_gvar.Create(gvar_name,gvar_val))
Además, los índices de las propias variables globales se cambian conforme van apareciendo nuevas variables. De ahí, que se produzca tanta confusión.
En definitiva, hay que sustituir el método CGlobalVar::Create por alguno otro menos activo. Se deberá añadir un constructor con los parámetros a la clase CGlobalVar, para que la variable pueda contarse en la lista.
Después de cambiar el método CGlobalVarList::LoadCurrentGlobals, tiene el siguiente aspecto:
//+------------------------------------------------------------------+ //| Load current global vars | //+------------------------------------------------------------------+ bool CGlobalVarList::LoadCurrentGlobals(void) { int gvars_cnt=GlobalVariablesTotal(); //--- for(int idx=0;idx<gvars_cnt;idx++) { string gvar_name=GlobalVariableName(idx); double gvar_val=GlobalVariableGet(gvar_name); datetime gvar_time=GlobalVariableTime(gvar_name); CGlobalVar *ptr_gvar=new CGlobalVar(gvar_name,gvar_val,gvar_time); if(CheckPointer(ptr_gvar)==POINTER_DYNAMIC) if(this.Add(ptr_gvar)>-1) continue; //--- return false; } //--- return true; }
Después de cambiar el método, el script funciona de manera normal. Obtenemos el siguiente mensaje:
2014.10.21 11:38:04.424 Globals_test3 (AUDUSD.e,H1) Cantidad de variables en la lista: 6
Añadimos la posibilidad de eliminar la impresión de la lista.
Ahora el script Globals_test3.mq5 tiene el aspecto siguiente:
//--- #include "CGlobalVarList.mqh" //--- input ENUM_GVARS_TYPE InpGvarType=GVARS_TYPE_FULL; // Set gvar type //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { CGlobalVarList gvarList; //--- delete gvars gvarList.SetGvarType(InpGvarType); //--- load current gvars gvarList.LoadCurrentGlobals(); Print("Imprimir lista antes de la eliminación."); gvarList.Print(10); //--- delete gvars if(gvarList.KillCurrentGlobals()) { Print("Imprimir lista después de la eliminación."); gvarList.Print(10); } }
Vamos a complicar un poco la tarea. Crearemos 10 variables globales de diferente tipo (fig.6).
Fig.6. Variables globales de diferente tipo
En nuestra propia lista gvarList , vamos a cargar solo las variables plenamente funcionales. Y luego las eliminaremos físicamente.
En el registro "Expertos", obtendremos la imagen siguiente:
MG 0 11:05:01.113 Globals_test3 (AUDUSD.e,H1) Imprimir lista antes de la eliminación. KL 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) OI 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) ---===Lista local===--- QS 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Tipo de variables globales: GVARS_TYPE_FULL RI 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Variables globales en total: 10 EG 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Variables globales en la lista actual: 5 RN 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #1, nombre - gVar10_prog_test1_scr, valor - 16.6400000000 KP 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #2, nombre - gVar2_prog_test1_scr, valor - 4.6400000000 GR 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #3, nombre - gVar4_prog_test1_scr, valor - 7.6400000000 RD 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #4, nombre - gVar6_prog_test1_scr, valor - 10.6400000000 LJ 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #5, nombre - gVar8_prog_test1_scr, valor - 13.6400000000 EH 0 11:06:18.675 Globals_test3 (AUDUSD.e,H1) Imprimir la lista después de la eliminación. FS 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) JJ 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) ---===Lista local===--- HN 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) Tipo de variables globales: GVARS_TYPE_FULL KH 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) Variables globales en total: 5 QP 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) Variables globales en la lista actual: 0
Se ha creado correctamente una lista que incluye solo las variables globales plenamente funcionales.
A continuación, ha sido vaciada y quedan 5 variables globales en el terminal (fig.7).
Fig.7. Variables globales temporales
Es decir, se ha cumplido con lo pensado.
En la clase CGlobalVarList se han implementado los métodos para guardar datos en el archivo y cargar datos desde el archivo.
4. Sentido práctico
Como ya sabemos, MQL4/5 es un lenguaje especializado de programación. Se creó para programar estrategias comerciales. Por eso, cualquier mecanismo en el lenguaje debe ser estudiado como un medio para formalizar una cierta idea comercial concreta.
Existen muchos ejemplos de "conexión" de asesores por medio de variables globales en un recurso MQL5, por eso se propone estudiar la situación cuando hay que controlar la ejecución de un programa.
Supongamos que tenemos el siguiente código de un robot comercial "Globals_test_EA", que usa el enfoque modular:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { Main(); }
donde el módulo principal tiene el aspecto siguiente:
//+------------------------------------------------------------------+ //| Main module | //+------------------------------------------------------------------+ void Main(void) { //--- establecer las etiquetas para todos los módulos for(int idx=0;idx<GVARS_LIST_SIZE;idx++) SetFlag(idx,false); //--- Comprobando si es posible comerciar y la existencia de conexión //--- permiso para comerciar if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) //--- conexión al servidor comercial if(TerminalInfoInteger(TERMINAL_CONNECTED)) //--- permiso para comerciar con el asesor iniciado if(MQLInfoInteger(MQL_TRADE_ALLOWED)) { //--- 1) módulo de apertura Open(); //--- 2) módulo de cierre Close(); //--- 3) módulo de trail Trail(); } }
Es decir, el principal incluye 3 componentes:
- el módulo de apertura;
- el módulo de cierre;
- el módulo de trail;
Ahora se pueden crear variables globales que controlen las etapas de ejecución del programa.
En total habrá 3 etapas configuradas en módulos. Para cada etapa actúan conjuntamente 2 puntos de control. El primero controlará el comienzo del funcionamiento del módulo, y el segundo, la finalización.
Los puntos de control se implementan en forma de variables globales.
En total se necesitan 6 variables con nombre:
//--- variables globales: nombres string gVar_names[6]= { "gvarOpen_start","gvarOpen_finish", "gvarClose_start","gvarClose_finish", "gvarTrail_start","gvarTrail_finish" };
Las etiquetas para todos los módulos se establecen al principio de la función Main(), y se desechan en cada módulo por separado. Y, naturalmente, solo las "propias". Por ejemplo, recurrimos al módulo Open():
//+------------------------------------------------------------------+ //| Open module | //+------------------------------------------------------------------+ void Open(void) { Comment(curr_module+__FUNCTION__); //--- if(!IsStopped()) { //--- desechar la etiqueta de incio del módulo SetFlag(0,true); //--- supongamos que el módulo funciona cerca de 1.25 seg { Sleep(1250); } //--- desechar la etiqueta de finalización del módulo SetFlag(1,true); } }
Al ejecutar el módulo en la ventana del gráfico, aparecerán comentarios referentes a que el programa está funcionando en el bloque Open().
A continuación, si el programa no se ha finalizado a la fuerza, el control pasa a la función de establecimiento/desechado de la etiqueta correspondiente. En caso de que la etiqueta no se haya podido desechar en cierto punto, consideraremos que el módulo no ha finalizado su trabajo.
De un modo esquemático, mostramos el proceso de seguimiento de las etapas de funcionamiento de los módulos mediante variables globales en la fig.8.
Fig.8. Diagrama de la secuencia de procesado de etiquetas
Por ejemplo, el asesor "Globals_test_EA" se encuentra en el gráfico y funciona en su modo habitual.
Lo he eliminado del gráfico, recibiendo a continuación este mensaje en el registro:
2014.10.22 20:14:29.575 Globals_test_EA (EURUSD.e,H1) El programa ha sido finalizado forzosamente antes de su ejecución: <<Open_finish>>
De esta forma, la interrupción del funcionamiento del asesor ha tenido lugar en el módulo Open().
Abrimos las variables globales mediante la tecla F3 (fig.9).
Fig.9. Variables globales para el asesor "Globals_test_EA"
A juzgar por la lista, en el cero se desechó solo la etiqueta responsable del comienzo del funcionamiento del módulo Open().
Resulta que, de manera potencial, se pueden observar problemas al no ejecutarse los comandos relacionados con la apertura de posición, su cierre y seguimiento.
Una nueva iniciación del funcionamiento en el gráfico mostrará en el registro el siguiente mensaje:
RQ 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Valor diferente a cero para: <<Open_finish>> CL 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Valor diferente a cero para: <<Close_start>> DH 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Valor diferente a cero para: <<Close_finish>> ES 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Valor diferente a cero para: <<Trail_start>> RS 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Valor diferente a cero para: <<Trail_finish>>
De esta forma, obtenemos la advertencia de no haber superado las etapas de funcionamiento del programa. ¿Qué hacemos si no han sido superadas? Eso ya es otra cuestión.
Conclusión
En este artículo he demostrado las posibilidades orientadas a objetos del lenguaje MQL5 en lo referente a la creación de objetos responsables del trabajo con las variables globales del terminal.
Como ejemplo práctico, se ha estudiado el caso en el que las variables globales pueden ser usadas como puntos de control de la ejecución de las etapas del programa.
Como siempre, cualquier observación, propuesta o crítica constructiva será bienvenida.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1210
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso