English Русский Español Deutsch 日本語 Português
MQL5 编程基础: 终端中的全局变量

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

MetaTrader 5示例 | 9 十二月 2016, 12:42
5 130 1
Dmitry Fedoseev
Dmitry Fedoseev

目录


图 1. 全局变量的代码片段

请不要把终端的全局变量与熟知的程序中的全局变量(图2)相混淆,可以尝试在其他编程语言中找到它们对应的部分。


图 2. 终端实例中 MovingAverage EA 的代码片段。程序的全局变量使用红色做了突出显示。

因为终端的全局变量在其他编程语言中没有恰好的对应内容,它们在MQL初学者中并不常用。因为他们不知道为什么和如何在他们的应用程序中使用,因为这些函数的名称相对复杂: GlobalVariableSet(), GlobalVariableGet(), GlobalVariableCheck(), 等等。

终端全局变量的主要特性是,即使终端关闭之后它们仍然能够保持它们的数值,这就是为什么它们在开发可靠的在订单之间有复杂交互的EA交易时,提供了一种简单快速的方法来保存重要数据。在您掌握了全局变量之后,您将无法想象在MQL5中开发EA交易时不去使用它们。

本文提供了实用的示例,使您可以学习到所有用于操作全局变量的函数,检查它们的特性和应用的模式,并且开发了一个类用于更加简单和快速地操作它们。

用于操作全局变量的函数可以在 MQL5 文档中的这里找到。 

在终端中查阅全局变量

在终端中执行以下命令: 主菜单 — 工具 — 全局变量. 全局变量的窗口就出现了 (图 3).

 
图 3. 终端中的全局变量窗口

基本上所有的全局变量操作都可以通过编程方法来执行,但是,这个窗口可能在测试EA交易时有用。它使您可以查阅所有的终端全局变量,并且可以编辑它们的名称和数值。如需修改一个变量,只要点击它的名称或者数值栏位。另外,这个窗口也可以用于创建新的变量,为此只需点击右上角的“增加”按钮。“删除”按钮是用于删除全局变量的,在删除之前,要点击所需的变量以使得该按钮被激活。练习创建,修改和删除全局变量。请注意: 如果在您打开窗口时窗口中已经包含一些变量,请保留它们不要改动,因为它们可能是用于您帐户中加载的EA交易的。

一个全局变量包含三个属性: 名称,数值和时间。时间栏位显示了变量最后访问的时间,如果最后访问的时间超过了四个星期,变量会自动被删除。因而,如果变量保存着重要数据,应该不时访问它以延长它的生命周期。  

创建一个终端中的全局变量

当给变量赋值时,变量会自动生成,如果已经有了给定名称的变量,它的数值会被更新。GlobalVariableSet()函数就是用于给变量赋值的。有两个参数传给这个函数: 变量名称 (字符串) 和它的数值 (双精度类型)。让我们尝试创建一个变量,打开 MetaEditor, 创建一个脚本程序并在它的 OnStart() 函数中写下如下代码:

GlobalVariableSet("test",1.23);

 执行脚本并在终端中打开全局变量窗口,窗口中应该包含一个新的叫做"test"的变量,并且它的数值为 1.23 (图 4)。

 
图 4. 带有新的 "test" 变量的全局变量窗口片段

实例的代码可以在 sGVTestCreate 脚本中找到。  

取得终端全局变量的值

在前面例子的脚本完成工作之后,该变量依然存在,让我们看一下它的数值。GlobalVariableGet() 函数就是用于取得数值的,有两个调用函数的方法,当使用第一种时,只需要把名称传给函数,函数会返回双精度型数值:

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

当运行代码时,会打开窗口显示 1.23 的数值。该实例可以在下面附件中的 sGVTestGet1 脚本中找到。

当使用第二种方法时,要把两个参数 – 名称和用于保存数值的变量(第二个参数是以引用方式传入的) – 传给函数并根据结果返回 true 或者 false:

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

结果会在窗口中显示 "true 1.23" 的信息。

如果我们试图取得不存在变量的数值,函数会返回 false 和 0。让我们把前面的代码例子少许改动: 当声明时为 'val' 变量赋值为 1.0,而尝试读取不存在的 "test2" 变量的数值:

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

结果就会打开窗口显示 "false 0.0" 的消息。本例可以在下面附件中的 sGVTestGet2-2 脚本中找到。

当使用第一种方法调用函数时,在变量不存在时我们也会得到0.0的数值,但是,也会接收到错误。通过使用第一种方法调用函数并做错误检查,我们就能够取得第二种调用方法类似的结果:

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

代码运行的结果(该实例可以在 sGVTestGet1-2 脚本中找到)是, 打开窗口显示 "0.0 4501" 的消息。0.0 是个数值, 4501 是一个错误代码 — "客户终端中的全局变量没有找到"。这不是一个关键错误,而只是通知。如果算法允许,您可以引用不存在的变量。例如,您想跟踪取得最大余额:

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

这代码即使在 "max_equity" 变量不存在的情况下也能正确运行。首先, MathMax() 函数会在实际余额与之前保存在 "max_equity" 变量中的数值选择最大值,因为变量不存在,我们就得到真实余额的数值。

全局变量的名称

我们可以看到,全局变量的名称是一个字符串,对变量名称的字符和顺序都没有限制,任何能在键盘上打出来的字符都可以用在名称中, 包括空格和在文件名称中禁用的字符。但是,我建议选择简单而易于阅读的名称,包括字符,数字和下划线,就和通用的变量一样。

在选择全局变量名称时只有一个较大的局限 — 名称的长度: 不能超过63个字符。

检查变量是否存在

GlobalVariableCheck() 函数就是用于检查变量是否存在的,传给函数的唯一参数是 — 变量的名称,如果变量存在,函数返回 true, 否则, false。让我们检查 "test" 和 "test2" 变量是否存在:

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

本例可以在下面附件中的 sGVTestCheck 脚本中找到。脚本运行的结果是受到 "true false" 的消息 — "test" 变量存在, 而 "test2" 变量不存在。

有的时候,有必要检查变量是否存在,比如,当跟踪获取最小余额时,如果我们在上面的例子中使用 MathMin() 函数替代 MathMax() 函数,它就不会正确工作,而永远返回 0 的结果。

在这样的情况下,检查变量是否存在就是有帮助的:

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

如果变量存在,使用 MathMin() 函数选择最小值,否则,马上给余额变量赋值。

全局变量的时间

我们已经在图3中看到,可以使用 GlobalVariableTime() 函数取得全局变量的时间。传给函数的唯一参数是 — 变量的名称,该函数返回 datetime 类型的数值:

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

代码可以在下面附件中的 sGVTestTime 脚本中找到。变量的时间属性只有在访问它的时候会改变, 也就是当使用 GlobalVariableSet() 和 GlobalVariableGet() 函数时,没有其他函数可以改动变量的时间值。如果我们通过全局变量窗口人工改变变量,它的时间也会改变(不论我们改变它的数值还是名称),

我们已经说过,变量会在它最后被访问后存在四个星期,之后会被终端自动删除。 

查阅所有的全局变量

有时候,我们需要一个全局变量,而不知道它的准确名称,我们可能记得开头而不记得结尾,例如: gvar1, gvar2, 等等。为了找到这样的变量,我们需要迭代终端中的所有全局变量并检查它们的名称。为此,我们需要 GlobalVariablesTotal() 和 GlobalVariableName() 函数。GlobalVariablesTotsl() 函数返回全局变量的总数,GlobalVariableName() 函数根据索引返回变量的名称。传给这个函数的是一个单独的 int 类型的参数。首先,让我们浏览所有的变量并在消息框中显示它们的名称和数值:

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

结果会打开窗口显示所有的变量名称和数值 (图 5). 代码可以在下面附件中的 sGVTestAllNames 脚本中找到。

 
图 5. 消息窗口中包含了终端中的所有全局变量

让我们增加一项新的检查来查看在名称中有某种特性的变量,下面的代码展示了,检查以"gvar"开头的变量名称 (该实例可以在下面附件中的 sGVTestAllNames2 脚本中找到):

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

检查是通过使用 StringFind() 函数来进行的。如果您想提高使用操作字符串函数的技巧,请阅读MQL5 编程基础: 字符串的文章。

删除全局变量

GlobalVariableDel()函数用于删除一个全局变量,它只接收一个参数 — 变量的名称。删除之前创建的 "test" 变量 (sGVTestDelete 脚本在下面的附件中):

GlobalVariableDel("test");

为了检查运行的结果,您可以使用 sGVTestGet2-1 或者 sGVTestGet2-2 脚本程序,或者打开全局变量窗口。

删除单个变量是简单的,但是您也许经常需要删除多个变量,GlobalVariablesDeleteAll() 函数就是用于此的。该函数可以传入两个可选的参数,如果我们不用参数调用函数,所有的全局变量都回被删除,通常,只需要删除一组含有相同前缀(名称的开头)的全局变量,第一个函数参数是用于指定前缀的。让我们使用这个函数作实验。首先,我们应该使用不同的前缀创建一些变量:

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

该代码创建四个变量: 两个含有 gr1_ 前缀, 另外两个有 _gr2 前缀。代码可以在下面附件中的 sGVTestCreate4 脚本中找到。通过运行 sGVTestAllNames 脚本来检查脚本程序的运行结果 (图 6)。

 
图 6. 通过 sGVTestCreate4 脚本程序创建的变量

现在,让我们删除以 gr1_ 开头的变量(下面附件中的 sGVTestDeleteGroup 脚本):

GlobalVariablesDeleteAll("gr1_");

在执行代码之后,再次使用 sGVTestAllNames 脚本程序来浏览所有的全局变量 (图 7)。我们可以看到所有的变量,而两个以 gr1_ 开头的除外。

 
图 7. 使用 gr1_ 开头的变量已经被删除

GlobalVariableDeleteAll() 函数的第二个参数是在您需要删除旧的变量时使用的。在这个参数中可以指定日期时间,如果变量的最后访问时间早于指定的数值,变量就会被删除。请注意,只有时间更早的变量会被删除,而更新或者相同时间的变量会被保留。变量可以额外根据前缀进行选择,如果不需要根据前缀进行选择,第一个参数会默认设为 NULL :

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

在实际应用中,根据时间删除变量只在一些很不常见的任务中需要,所以我们就不在这个主题中花费太多时间了。

GlobalVariablesFlush 函数

当关闭终端时,全局变量会自动保存到文件中,终端启动时就能再次读取它们。当使用全局变量时不需要知道这个过程的所有细节 (文件名称,数据存储格式, 等等.)

如果终端紧急关闭,全局变量就可能丢失。GlobalVariableFlush() 函数就帮您避免这一点。该函数强制保存全局变量。在变量通过使用 GlobalVariableSet() 函数设置或者变量被删除之后,就调用 GlobalVariableFlush() 函数,函数的调用不使用参数: 

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

该代码可以在附件中的 sGVTestFlush 文件中找到。 

最好能演示 GlobalVariableFlush() 函数的运行过程,但是很不幸,我们没办法模拟终端紧急关闭而全局变量消失的场景。终端的运行可以通过任务管理器的进程页面来进行终止,也许,全局变量可能会在电脑断电的时候消失,现在电脑很少会突然断电,因为很多用户使用笔记本电脑,而台式电脑通常有不间断电源设备。如果终端在服务器上运行,防止断电的措施就更加重要。所以,就算没有 GlobalVariableFlush() 函数,全局变量也是非常可信赖的保存数据的方法。    

临时变量, GlobalVariableTemp 函数

GlobalVariableTemp() 函数创建一个临时全局变量 (存在直到终端被关闭)。在我使用MQL5开发EA交易的几年中,我从未遇到使用这样变量的需要。并且,临时全局变量的概念与它们应用的基本原则相反 — 长期保存数据而不受终端重新启动而影响。但是,因为MQL5语言中有这样的函数,我们也应该注意一下,以防您需要它。

当调用该函数时,会有单独一个参数 — 变量名称 — 被传递给它。如果这样名称的变量不存在,会创建一个临时变量而且数值为0。在那以后,使用 GlobalVariableSet() 函数来给它赋值,这样它就能正常使用了。如果变量已经存在 (之前通过 GlobalVariableSet() 函数创建过了), 它不会被转化为临时变量:

   GlobalVariableSet("test",1.2); // 设置变量值以确保变量存在
   GlobalVariableTemp("temp"); // 创建一个临时变量
   Alert("创建后的临时变量值 - ",GlobalVariableGet("temp"));
   GlobalVariableSet("temp",3.4); // 设置临时变量值
   GlobalVariableTemp("test"); // 尝试把 "test" 变量转化为临时变量

该实例可以在下面附件中的 sGVTestTemp 文件中找到。在运行脚本之后,打开全局变量窗口,它应该包含 "temp" 变量,值为 3.4 以及 "test" 变量,值为 1.2。关闭全局变量窗口,重新运行终端并再次打开窗口,"test" 变量还保存在那里,而 "temp" 就不在了。

根据条件修改变量, GlobalVariableSetOnCondition 函数

现在,是时候探讨最后一个,并且以我的观点是最有趣的函数了: GlobalVariableSetOnCondition()。这个函数要传入三个参数: 名称,新的数值,以及测试值。如果变量值等于测试值,它就改为新的数值而函数会返回true, 否则它就返回 false (如果变量不存在也会这样)。

对于运行原则,函数使用与以下代码类似:

   double check_value=1;
   double value=2;

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

如果 "test" 全局变量等于 check_value, 就把新的值赋给它并且返回 true ,否则 — false。check_value 变量的默认值为 1, 所以,如果“test”全局变量不存在,就会返回 false

GlobalVariableSetOnCondition() 函数的主要目的是为多个EA交易提供一致的执行。因为现在的操作系统都是多任务程序,而每个EA交易可以看作是独立的线程,没有人能够保证所有的EA交易能够挨个执行它们的任务,

如果您对使用 MetaTrader 4 有些经验的话, 您可能会记得繁忙的交易流。现在,多个EA交易可能会同时向服务器发送交易请求,而它们将会被执行,与之前一次只有一个EA发送请求的情况不同,如果终端中有多个EA交易,繁忙的交易流可能会使执行市场操作时经常出错。当建立和关闭订单时,错误会更加严重,因为好的EA交易会重复尝试关闭或者建立仓位。另外,不同EA交易在同一个点建立和关闭仓位,有时也有这种情况。另外,如果跟踪止损功能(EA内部构建而并非在终端中设置)在多个EA交易中激活,每个订单时刻将只有它们中的一个可以修改止损,这已经是一个问题。尽管可能现在没有这些问题,但是有些EA交易还是需要任务按顺序执行。 

上面所述的全局变量就可以用于保证一组EA能够一致工作。在 OnTick() 函数执行的开始,给变量赋值,这样其他EA交易就能看到有些EA交易正在运行,它们应当终止它们的 OnTick()函数,或者进入循环等待。在EA交易完成所有必需的操作之后,我们把 check_value 赋给变量,现在其他 EA 就可以执行它的 OnTick() 函数了,如此这般。

上面显示的代码可以解决问题,但是我们无法确保执行序列:

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

将立即向后执行: 

GlobalVariableSet("test",value);

另一个 EA 可能会在侦测过 check_value 之后,在中间打断它们,在执行过它的部分任务后,第一个EA交易再继续运行,这样,两个EA交易可能会同步运行。GlobalVariableSetOnCondition() 函数就解决了这样的问题。在文档中已经提到, "函数对全局变量的访问是原子方式的",原子方式意思就是"无法分割",所以,没有其它的应用程序可以在数值的检验和赋予新值之间打断它们。

这个函数的唯一缺点是变量不存在时不会创建变量,这意味着我们应该执行额外的检查 (最好在EA初始化期间进行) 并生成变量。 

让我们写两个EA交易来进行一次试验。两个EA交易完全相同,在 OnTick() 函数的开头会显示"EA1 开始"消息的窗口,之后是三秒的暂停 (Sleep() 函数),最后显示"EA1 结束"的消息:

void OnTick(){
   Alert("EA1 开始");   
   Sleep(3000);   
   Alert("EA1 结束");
}

第二个 EA 很类似, 只是消息是不同的: "EA2 开始" 和 "EA2 结束"。附件中的EA命名为 eGVTestEA1 和 eGVTestEA2。在终端中打开两个相同的图表并在其中附加上EA交易,消息窗口显示,EAs 同时开始和结束了它们的工作 (图 8)。


图 8. 关于 OnTick() 函数执行开始和结束的 EA 消息

现在,让我们使用 GlobalVariableSetOnCondition() 函数来提供一致的 EA 操作,插入的变化对于两个EA来说是相同的,所以,让我们在包含文件中写入代码,该文件称为 GVTestMutex.mqh (附加在下面)。

现在,让我们探讨 GVTestMutex.mqh 文件中的函数。在 EA 初始化过程中检查全局变量是否存在,并且如有必要则进行创建 ( Mutex_Init() 函数),只有一个参数 — 变量名称 — 被传给函数:

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

第二个函数 (Mutex_Check()) 是用于验证的,在函数中会循环等待全局变量被释放,变量一被释放,函数就返回 true, 而 EA 继续执行它的 OnTick() 函数,如果变量在一定时间内没有被释放,函数返回false。在这种情况下 OnTick() 函数的执行应被中止:

bool Mutex_Check(string name,int timeout){   
   datetime end_time=TimeLocal()+timeout; // 等待结束的时间
   while(TimeLocal()<end_time){ // 在一定时间内循环
      if(IsStopped()){
         return(false); // 如果EA被从图表上删除
      }
      if(GlobalVariableSetOnCondition(name,1,0)){ 
         return(true);
      }
      Sleep(1); // 小的暂停
   }   
   return(false); // 未能等到释放
}

全局变量的名称和等待时间的秒数会传给这个函数。

第三个函数是 Mutex_Release(),它把全局变量值设为 0 (释放),这样其他的EA交易就能够开始它们的工作了:

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

复制一份EA,包含这个文件并在其中加入一个函数调用,变量名称为 "mutex_test",让我们使用30秒的超时时间来调用 Mutex_Check() 函数,完整的 EA 代码在下面显示:

#include <GVTestMutex.mqh>

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

void OnTick(){

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

   Alert("EA1 开始");
   Sleep(3000);
   Alert("EA1 结束");

   Mutex_Release("mutex_test");

}

让我们复制一份这个EA并修改显示消息的文字,附件中的EA被命名为 eGVTestEA1-2 和 eGVTestEA2-2,在两个类似的图表中运行EA,它们现在是按顺序运行的了 (图 9)。

 
图 9. EA按顺序运行

请注意 time-out 参数: 要把时间设得超过一组 EA 运行时间的总和,也可能出现有些 EA 在被从图表中删除时正在运行 OnTick() 函数,而没有执行 Mutex_Release() 函数,在这种情况下,其他EA就无法等到它返回了,所以,对于 time-out 超时的状况,我们应该把全局变量设为0,或者找到其他方法来跟踪它,这依赖于指定的任务,有时候,EA的并行运行是可以接受的,而其他一些状况时,它们应该按顺序执行。

用于简化全局变量操作的类

请注意以下几点以更加方便地操作全局变量。

  1. 每份复制的EA有必要使用唯一的变量名称。
  2. 在测试器中使用的变量名称应该和用在账户中的不同。
  3. 如果EA交易在测试器中运行,EA应该在每次测试运行后删除所有它所创建的变量。
  4. 为操作全局变量提供更加方便的函数调用,使函数名称更短。
当在EA交易中使用全局变量时,可能要把它们分为两种类型: 通用的和绑定到订单的。通用的变量用于保存与EA操作相关的通用数据: 例如,一些事件的时间,一组仓位的最大利润,等等。绑定到订单的变量包含额外的与单个订单(或者仓位)的数据: 例如,在手数增加序列中的订单的索引,请求中的建仓价格,等等。每个订单都有其自己唯一的索引 — 订单编号,所以,我们只需根据订单编号构建名称来定义保存数据变量的名称(例如, "index", "price"),订单编号是 ulong(无符号长整型)类型的变量,最大长度是20个字符,而最大变量长度为63,说明我们还可以使用43个字符。

当为通用变量构建名称时会有些复杂,让我们粗略估计一下可能的变量长度。我们可以用于区分变量的第一个属性是EA的名称,可能包含20个字符。相同的EA交易可能用在不同的交易品种上,也就是说第二个唯一的特性是交易品种 (多于4个字符)。工作于相同交易品种的EA可以有不同的订单ID ulong 类型的幻数 (最大长度 — 20 个字符)。可能在一个终端中切换不同的账户,账户编号是一个长整型(long)变量 (最大长度 — 19 个字符长),对于允许的变量长度,我们总共有63个字符,而仅仅是前缀就可能达到这个长度!

这意味着我们必须牺牲一些东西。让我们遵循此原则: 一个终端只运行一个账户。如果您有多个账户,为每个账户设置单独的终端实例。这就意味着我们可以去掉账户标号,这样最大前缀大小就减少20个字符而减少到43个字符长了。我们还可以加上另一条原则: 不要使用长的幻数。最终,还应该注意EA交易的名称,它们的名称应该短一些。这样从EA的名称,交易品种和幻数中构建全局变量的名称就是可以接受的了,也许,您将有更加方便的方法来构建名称,但是让我们继续文章中的这个方法,

让我们写一个类。这个类叫做 CGlobalVariables, 文件本身叫做 CGlobalVariables.mqh 并且已经在下面的附件中。在 'private' 部分声明两个用于变量前缀的变量: 第一个 — 用于通用变量,第二个 — 用于绑定到订单的变量。

class CGlobalVariables{
   private:
      string m_common_prefix; // 通用变量的前缀
      string m_order_prefix; // 订单变量的前缀
   public:
      // 构造函数
      void CGlobalVariables(){}
      // 析构函数
      void ~CGlobalVariables(){}
}

让我们在 'public' 部分创建 Init() 方法,此方法可以在 EA 初始化的时候调用,有两个参数 — 交易品种和幻数 — 会传递给它。在这个方法中会构建前缀,订单变量的前缀很简单,您只需要把EA在账户中运行时的变量和EA在测试器中运行时的区分开。所以,账户中的订单变量以"order_"开始, 而测试器中 — 用 "tester_order_"。也可以只用 "t_" 来加到测试中的通用变量前缀中 (因为它们是唯一的,另外我们也应该节约使用字符),旧的全局变量应该在测试器初始化时被删除。当然,它们也应该在终止时被删除,但是我们无法确保测试的结果,因为变量可能会保留下来。现在,让我们创建 DeleteAll() 方法并调用它。我建议把方法放到 'private' 部分,代码晚些时候会加上。下面显示了 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();
   }         
}

让我们增加一个方法来返回通用变量的前缀,因为它可能会在某些使用全局变量的特定任务中使用:

string Prefix(){
   return(m_common_prefix);
} 

增加基本的方法: 用于检查,设置,取得数值以及删除独立的变量。因为我们有两个前缀,我们应该两个同名的函数方法(两个函数名称相同,但是参数不同),只有一个参数 — 变量名称 — 用于传入一组方法。这些是用于通用变量的方法。传给另一组方法的是订单编号和变量名称,这些是用于订单变量的方法:

// 用于通用方法
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); 
}
// 用于订单变量
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); 
} 

让我们回到 DeleteAll() 方法并写下代码根据前缀删除变量:

GlobalVariablesDeleteAll(m_common_prefix);
GlobalVariablesDeleteAll(m_order_prefix);  

可以在测试器中测试过后删除变量,所以让我们增加 Deinit() 方法,可以在EA终止时调用:

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

为了提高全局变量的可靠性,我们应该使用 GlobalVariablesFlush() 方法。让我们为函数增加另外一个方法,在类中调用方法比写长名称的函数(实现第四点的要求)简单得多:

void Flush(){
   GlobalVariablesFlush();
}

有时候,您可能需要把通用变量分组,给它们增加额外的前缀并且在EA的操作中删除这些组。让我们增加另外一个 DeletByPrefix() 方法:

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

这样,我们就获得了足够的类功能,使得我们可以解决使用全局变量中95%的任务,

如需使用这个类,只要在EA交易中包含文件:

#include <CGlobalVariables.mqh>

创建一个对象:

CGlobalVariables gv;

在EA初始化时调用 Init() 方法,并向其中传入交易品种和幻数:

gv.Init(Symbol(),123);

在终止时调用 Deinit() 方法来从测试器中删除变量:

gv.Deinit();

在那之后,我们在开发EA时所需要做的就是使用 Check(), Set(), Get() 和 Delete() 方法,只要给它们传入变量名称的特别部分,例如:

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

EA 运行的结果是,名为 eGVTestClass_GBPJPY_123_name1 的变量名称会出现全局变量的列表中 (图 10)。

 
图 10. 使用 CGlobalVariables 类创建变量的全局变量窗口片段

变量的长度是 29 个字符,以为着我们选择变量名称还有较大空间。对于订单变量,我们需要传入订单编号,而不必经常构建完整的名称并调用 IntegerToSTring() 函数来把编号转换为字符串,这样极大简化了对全局变量的使用。下面附件中的 eGVTestClass EA 就是这个类的使用实例。

也可以对这个类进行少许改动以更加方便使用。现在,是时候提高类的构造函数和析构函数了,让我们在构造函数中增加对 Init() 方法的调用,而在析构函数中调用 Deinit() 方法:

void CGlobalVariables(string symbol="",int magic=0){
   Init(symbol,magic);
}
// 构造函数
void ~CGlobalVariables(){
   Deinit();
}

在那以后,就没有必要调用 Init() 和 Deinit() 方法了,而我们只需要在创建类的实例时指定交易品种和幻数:

CGlobalVariables gv(Symbol(),123);

结论

在本文中,我们检视了所有用于操作终端全局变量的函数,包括 GlobalVariableSetOnCondition() 函数。我们还创建了一个类用于在创建EA时极大简化全局变量的使用。当然,这个类没有包含所有与全局变量相关的的特性,但是它还是具有了最有必要和最常用的部分,您可以继续提高它或者在有必要时开发您自己的类。 

附件

  • sGVTestCreate — 创建一个变量。
  • sGVTestGet1 — 取得数值的第一个方法。
  • sGVTestGet2  — 取得数值的第二个方法。
  • sGVTestGet1-2 — 取得一个不存在变量数值的第一个方法。
  • sGVTestGet2-2 — 取得一个不存在变量数值的第二个方法。
  • sGVTestCheck — 检查变量是否存在。
  • sGVTestTime — 取得变量的时间。
  • sGVTestAllNames — 取得所有变量的名称列表。
  • sGVTestAllNames2 — 取得指定前缀的变量名称列表。
  • sGVTestDelete — 删除一个变量。
  • sGVTestCreate4 — 创建四个变量 (两组,每组两个)。
  • sGVTestDeleteGroup — 删除一组变量。
  • sGVTestFlush — 强制保存变量。
  • sGVTestTemp — 创建一个临时变量。
  • eGVTestEA1, eGVTestEA2 — 演示一个并行 EA 交易的运行。
  • GVTestMutex.mqh — 用于互斥(Mutex)开发的函数。
  • eGVTestEA1-2, eGVTestEA1-2 — 演示按顺序工作的EA交易。
  • CGlobalVariables.mqh — 用于操作全局变量的 CGlobalVariables 类。
  • eGVTestClass — 用于演示如何使用 CGlobalVariables 类的EA交易。



本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/2744

附加的文件 |
files.zip (10.44 KB)
最近评论 | 前往讨论 (1)
Rubing Zhu
Rubing Zhu | 6 3月 2021 在 04:56
都看完了  受益匪浅,就是最后的部分有些复杂,临时变量还是有用的   我在EA中有操盘面板  面板中包括 开启移动止损功能按钮,临时变量就能记住变量的 数值  再切换周期中 我能获取原按钮状态  进行重画 谢谢你的文章
图形界面 X: 标准图表控件 (集成编译 4) 图形界面 X: 标准图表控件 (集成编译 4)
这一次我们将研究标准图表控件。它可以创建具有同步水平滚动功能的子图表数组。此外, 我们将继续优化库代码以降低 CPU 负载。
图形界面 X: 简单快速开发库的更新 (版本 3) 图形界面 X: 简单快速开发库的更新 (版本 3)
在本文中,我们介绍下个版本的简单快速开发库(版本 3),它修改了一些缺陷,并且加入了新的功能,文章中有更加详细的内容。
海龟汤和海龟汤升级版的改进 海龟汤和海龟汤升级版的改进
本文介绍了来自琳达.布拉福德.瑞斯克(Linda Bradford Raschke)和劳伦斯.A.康纳斯(Laurence A. Connors)的《华尔街智慧:高胜算短线交易策略(Street Smarts: High Probability Short-Term Trading Strategies)》一书的两个交易策略,‘海龟汤’和‘海龟汤升级版’的原则规范。在书中描述的策略非常流行,但是有必要知道的是,作者是基于15年到20年的市场行为来开发它们的。
图形界面 X: 简单快速开发库的更新 (版本 2) 图形界面 X: 简单快速开发库的更新 (版本 2)
自从之前的系列文章发布以后,简单快速开发库(Easy And Fast library)又增加了一些新的功能。库的结构和代码经过部分优化后部分减少了CPU的负载,很多控件类中的一些重复方法被转移到 CElement 基类中。