下载MetaTrader 5

MQL5 编程基础: 终端的全局变量

19 十一月 2014, 11:01
Dennis Kirichenko
0
1 386

介绍

在 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)。

变量名则通过添加程序名称和类型至基本组件来创建。

在我的案例中,它是:

  1. Gvar1 - 基本组件;
  2. prog_Globals_test1 - 创建变量的程序 (它的名称是 Globals_test1);
  3. 程序类型 - scr (脚本)。

按下 F3 键,下面的条目将出现在 MetaTrader 5 窗口的全局变量列表中:

图例.1. 变量 Test_temp_var1_prog_Globals_test1_scr 的值等于 3.18

图例.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. 创建临时全局变量的时间开销

图例.2. 创建临时全局变量的时间开销

类似的完全的全局变量的测试结果显示在图例.3。创建它们不需花费太久。

其背后的原因是,这些变量都保存到磁盘上位于 Profiles 文件夹中的 gvariables.dat 文件。

图例.3. 创建完全的全局变量的时间开销

图例.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. 全局变量简略集合

图例.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. 新的全局变量集合

图例.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. 多元全局变量

图例.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. 临时全局变量

图例.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 个组成部分:

  1. 开仓模块;
  2. 平仓模块;
  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. 处理标志序列模式

图例.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 的全局变量

图例.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 语言,用于创建对象,并与终端的全局变量协同工作。

使用全局变量作为控制点实现程序阶段服务的例子。

与往常一样,欢迎任何意见,建议和建设性的批评。


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/1210

附加的文件 |
cglobalvar.mqh (20.52 KB)
cglobalvarlist.mqh (18.74 KB)
globals_test_ea.mq5 (13.01 KB)
globals_test1.mq5 (3.67 KB)
globals_test2.mq5 (2.92 KB)
globals_test3.mq5 (2.59 KB)
为何在 MetaTrader 4 与 MetaTrader 5 上的虚拟托管优于一般的 VPS 为何在 MetaTrader 4 与 MetaTrader 5 上的虚拟托管优于一般的 VPS

虚拟托管云网络是专为 MetaTrader 4 和 MetaTrader 5 平台研发的,并拥有许多本地解决方案。获得我们的 24 小时免费服务 - 现在即可测试一台虚拟服务器。

MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作 MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作

这次,我们将要创建一款多币种 EA,交易算法基于限价订单 Buy Stop(高买) 和 Sell Stop(低卖)。本文讨论下列事项:在规定时间范围内进行交易,布置/修改/删除限价订单,检查最后一个持仓是否在止盈或止损位置平仓,以及在成交历史中控制每个品种。

视频教程: MetaTrader 的信号服务 视频教程: MetaTrader 的信号服务

仅有 15 分钟,这个视频教程解释了什么是 MetaTrader 的信号服务,并非常详细演示了如何订阅交易信号,以及如何成为服务的信号提供商。通过观看本教程,您将可以订阅任何交易信号,或者在我们的服务中发布并推广自己的信号。

MQL5 Cookbook: 处理 BookEvent MQL5 Cookbook: 处理 BookEvent

本文研究 BookEvent - 一个市场深度事件,以及它的处理原理。一个处理市场深度状态的 MQL 程序,作为例程。它采用面向对象方法编写。处理结果作为面板显示在屏幕上,还有市场深度级别。