MQL5 编程基础: 终端的全局变量
Denis Kirichenko | 19 十一月, 2014
介绍
在 MQL4/5 的环境里有个有趣的工具 - 客户端的全局变量。它允许创建用于终端所有程序的共享数据存储区域。此外,该区域的生存期不会因终端关闭而停止。本文建议采用面向对象编程工具,以便清晰的理解什么是终端的全局变量。
在文章的以后部分,除非另有说明,客户端全局变量将被称作“全局变量”。
1. 全局变量,函数
从一个程序员的角度,全局变量是一个已命名的内存区域,可用于交易终端的所有工作程序。新程序员应该注意的是,如果有几个终端同时工作,它们之中的每一个都有自己独立的全局变量内存空间。它们不会重叠。
该语言的开发者在文档中规定了 11 个函数可与全局变量工作。
有关理论可以在 MQL4 教科书的 "全局变量" 章节找到。
在下一章节我将使用 面向对象编程 工具来实现设置任务。
2. 类 CGlobalVar
以面向对象编程的思想指导,让我们创建一个类 CGlobalVar,它将直接负责一个全局变量的对象。
//+------------------------------------------------------------------+ //| 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); };
一个类要包括什么?对于一个最小的属性列表,我会选择以下属性:
- 变量名;
- 变量值;
- 创建时间;
- 最后调用时间;
- 临时变量特征。
而类方法,它们如下所见:
- 创建;
- 删除;
- 检查存在;
- 设置新值;
- 按条件设置新值;
- 接收名称;
- 接收值;
- 接收临时变量标志。
该 CGlobalVar::GetValue 方法应该分别论及。它是一个模板方法。它返回用户作为参数设置的变量值的数据类型。
这里的问题是,在 MQL 中,一个函数只能通过参数来传递。因此需要添加一个假参数。
让我们创建测试脚本 Globals_test1.mq5,它将与 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); } }
创建全局变量如下:
gVar1.Create("Gvar1",3.123456789101235,true)
第一个参数是将来的变量基本组件,变量名 ("Gvar1"),第二个参数是值 (3.123456789101235),第三个参数表明该变量是临时的 (true)。
变量名则通过添加程序名称和类型至基本组件来创建。
在我的案例中,它是:
- Gvar1 - 基本组件;
- prog_Globals_test1 - 创建变量的程序 (它的名称是 Globals_test1);
- 程序类型 - scr (脚本)。
按下 F3 键,下面的条目将出现在 MetaTrader 5 窗口的全局变量列表中:
图例.1. 变量 Test_temp_var1_prog_Globals_test1_scr 的值等于 3.18
当它启动并成功实现,以下条目在 "Experts" 日志中输出:
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
变量值的不同数据类型打印在日志中。
如果 MetaTrader 5 终端重启,则 Gvar1_temp_prog_Globals_test1_scr 变量从全局列表中消失。发生这样的情况是因为作为临时变量,它只能在终端打开时生存。
在 MQL4/5 中,全局变量接收数据时,没有办法查知变量是否为临时。识别一个临时变量的最简单方法,也许就是在变量名中添加一个关键字。例如,可以在变量名中附加后缀 "temp" 。然而,通过这种方式创建的全局变量名称有一个明显的缺点,特别是那些使用其它程序,而非 CGlobalVar 类创建的变量。
在某些时候,我想知道有多少,以及如何迅速地创建全局变量。
我稍微修改了一下之前的脚本,并把它命名为 Globals_test2.mq5。它将以不同的运行数量启动。我在每次运行并删除变量后重新启动终端。
#property script_show_inputs //--- #include "CGlobalVar.mqh" input uint InpCnt=10000; // Number of 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 creating a global variable!"); } //--- finish value uint time=GetTickCount()-start; //--- to print PrintFormat("Creation of %d global variables took %d ms",InpCnt,time); }
这是结果 (图例.2)。
图例.2. 创建临时全局变量的时间开销
类似的完全的全局变量的测试结果显示在图例.3。创建它们不需花费太久。
其背后的原因是,这些变量都保存到磁盘上位于 Profiles 文件夹中的 gvariables.dat 文件。
图例.3. 创建完全的全局变量的时间开销
我不认为有必要创建这么多的全局变量。我进行这种评估只是出于好奇。
在下一个里程,我们将用一组全局变量来工作。
3. CGlobalVarList 类
为规划与全局变量的工作,我们要创建一个 CGlobalVarList 类型的列表类全局变量。这个列表类型是标准列表类 CList 的子类。
类的声明可以表示为:
//+------------------------------------------------------------------+ //| 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); };
如果与当前全局变量连接的对象包含在 CGlobalVarList 类型列表之中,则使用 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; }
此方法读取所有全局变量,并将它们包含到列表中。
而 m_gvars_type 属性控制包含全局变量的类型。它是一个 ENUM_GVARS_TYPE 类型的枚举:
//+------------------------------------------------------------------+ //| Enumeration for gvars type | //+------------------------------------------------------------------+ enum ENUM_GVARS_TYPE { GVARS_TYPE_ALL=-1, // all global GVARS_TYPE_FULL=0, // only full GVARS_TYPE_TEMP=1, // only temporary };
让我们假设在 CGlobalVarList 列表初始化之前,有一个全局变量集合如图例.4 所示。
图例.4. 全局变量简略集合
我们要检查这个集合是否将要进行列表正确性处理。为进行检查,将要创建 Globals_test3.mq5 测试脚本。
#include "CGlobalVarList.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { CGlobalVarList gvarList; gvarList.LoadCurrentGlobals(); PrintFormat("Number of variables in the set: %d",gvarList.Total()); }
脚本启动之后,新的全局变量 (以黄色高亮显示) 出现,发生这种情况有点出乎意料 (图例.5)。
图例.5. 新的全局变量集合
打印的字符串如:
2014.10.21 11:35:00.839 Globals_test3 (AUDUSD.e,H1) Number of variables in the list: 10
此事发生,因为在 CGlobalVarList::LoadCurrentGlobals 方法的声明里有 CGlobalVar::Create 方法的引用。
这就意味着新的全局变量是在这个语句里面创建的:
if(ptr_gvar.Create(gvar_name,gvar_val))
此外,全局变量索引因为新的变量出现而改变。这就是因何原因造成的混乱。
我推荐用低活性的方法替代 CGlobalVar::Create 方法。带参数的构造函数必须加入 CGlobalVar 类中,这样就可以在列表中存取变量。
修改后的 CGlobalVarList::LoadCurrentGlobals 方法如下所示:
//+------------------------------------------------------------------+ //| 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; }
脚本在修改后工作正确。得到以下打印记录:
2014.10.21 11:38:04.424 Globals_test3 (AUDUSD.e,H1) Number of variables in the list: 6
然后,我们将添加功能,允许删除和打印清单。
现在 Globals_test3.mq5 脚本看上去像:
//--- #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("Print the list before deletion."); gvarList.Print(10); //--- delete gvars if(gvarList.KillCurrentGlobals()) { Print("Print the screen after deletion."); gvarList.Print(10); } }
我们继续完成任务,创建 10 个多元全局变量 (图例.6)。
图例.6. 多元全局变量
仅有完全的变量包含在我们的列表 gvarList 。之后它们将被删除。
日志 "Expert" 包括以下内容:
MG 0 11:05:01.113 Globals_test3 (AUDUSD.e,H1) Print the list before deletion. KL 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) OI 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) ---===Local list===--- QS 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Global variable type: GVARS_TYPE_FULL RI 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Total number of global variables: 10 EG 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Number of global variables in current list: 5 RN 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #1, name - gVar10_prog_test1_scr, value - 16.6400000000 KP 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #2, name - gVar2_prog_test1_scr, value - 4.6400000000 GR 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #3, name - gVar4_prog_test1_scr, value - 7.6400000000 RD 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #4, name - gVar6_prog_test1_scr, value - 10.6400000000 LJ 0 11:05:01.613 Globals_test3 (AUDUSD.e,H1) Gvar #5, name - gVar8_prog_test1_scr, value - 13.6400000000 EH 0 11:06:18.675 Globals_test3 (AUDUSD.e,H1) Print the list after deletion. FS 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) JJ 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) ---===Local list===--- HN 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) Global variable type: GVARS_TYPE_FULL KH 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) Total number of global variables: 5 QP 0 11:06:19.003 Globals_test3 (AUDUSD.e,H1) Number of global variables in the current list: 0
此列表仅包括正确创建地完全的全局变量。
然后将之清除,只在终端里留下 5 个临时变量 (图例.7)。
图例.7. 临时全局变量
预定任务完成。
在 CGlobalVarList 类中,保存数据至文件以及从文件中加载数据的方法均已实现。
4. 实际应用
众所周知,MQL 4/5 是一个专用的编程语言。它用来创建程序化的交易策略。这就是为什么该语言的所有工具,都被考虑用来正规化确定的交易想法。
在 MQL5 平台上有足够多的 EA 与全局变量链接的例程。今天,我建议仔细观察,何时需要实现程序控制的情况。
让我们设想有个基于基于模块化的 "Globals_test_EA" 交易机器人代码:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { Main(); }
主模块看着像:
//+------------------------------------------------------------------+ //| Main module | //+------------------------------------------------------------------+ void Main(void) { //--- set flags for all modules for(int idx=0;idx<GVARS_LIST_SIZE;idx++) SetFlag(idx,false); //--- Check the trade possibility and connectivity //--- permission to trade if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) //--- connection to the trading server if(TerminalInfoInteger(TERMINAL_CONNECTED)) //--- permission to trade for the launched EA if(MQLInfoInteger(MQL_TRADE_ALLOWED)) { //--- 1) opening module Open(); //--- 2) closing module Close(); //--- 3) Trailing Stop module Trail(); } }
这个主模块包括 3 个组成部分:
- 开仓模块;
- 平仓模块;
- 追随止损模块。
现在,我们需要创建全局变量用于程序执行的阶段控制。
模块的组成中有三个阶段。每个阶段有两个控制点。第一个点控制模块开始工作,第二个点控制模块结束工作。
控制点以全局变量的形式实现。
所以,我们需要六个全局变量,名称如下:
//--- global variables: names string gVar_names[6]= { "gvarOpen_start","gvarOpen_finish", "gvarClose_start","gvarClose_finish", "gvarTrail_start","gvarTrail_finish" };
所有模块的标志在 Main() 函数的开始设置,并且在每个单独的模块中被清除。不用说,我们正在谈论的就是 "own" 标志。例如,让我们参考 Open() 模块:
//+------------------------------------------------------------------+ //| Open module | //+------------------------------------------------------------------+ void Open(void) { Comment(curr_module+__FUNCTION__); //--- if(!IsStopped()) { //--- clear the module start flag SetFlag(0,true); //--- assume that the module operates for approximately 1.25 s { Sleep(1250); } //--- clear the module finish flag SetFlag(1,true); } }
在模块执行时,程序工作在 Open() 块中,会有注释出现在图表窗口。
然后,如果程序没有被强制关闭,则将控制传递到设置/清除相应标志的函数。万一,在任何控制点清除标志失败,该模块则认为没有完成工作。
与全局变量工作的模块阶段跟踪模式,表示在图例.8。
图例.8. 处理标志序列模式
例如,"Globals_test_EA" EA 被加载到图表,并操作正常。
当我从图表中删除 EA,以下条目出现在日志中:
2014.10.22 20:14:29.575 Globals_test_EA (EURUSD.e,H1) Program forced to terminate before execution: <<Open_finish>>
因此,中断 EA 位于 Open() 模块。
按下 F3 键打开全局变量列表 (图例.9)。
图例.9. 用于 "Globals_test_EA" EA 的全局变量
通过查看列表,只有负责 Open() 模块开始工作的标志被归零。
它看起来像故障,可以在开仓、平仓、修改命令执行失败时被检测到。
在同一图表里重新启动机器人,以下信息将会显示在日志里:
RQ 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Non-zero value for: <<Open_finish>> CL 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Non-zero value for: <<Close_start>> DH 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Non-zero value for: <<Close_finish>> ES 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Non-zero value for: <<Trail_start>> RS 0 20:28:25.135 Globals_test_EA (EURUSD.e,H1) Non-zero value for: <<Trail_finish>>
这样,我们就会收到关于程序阶段的故障报警。这导致了另外一个问题。如果这些阶段失败,可以做什么?这是另外一个故事。
结论
在本文中,我演示了 面向对象 的 MQL5 语言,用于创建对象,并与终端的全局变量协同工作。
使用全局变量作为控制点实现程序阶段服务的例子。
与往常一样,欢迎任何意见,建议和建设性的批评。