MQL5 编程基础: 终端的全局变量·综合运用
🗂️

MQL5 编程基础: 终端的全局变量·综合运用

(3/3)·从函数到类再到实战,把终端全局变量的生存期与隔离性一次讲透

新手友好 第 3/3 篇

不少人在多终端并行时误以为全局变量是跨端共享的,结果程序间互相踩内存。其实每个终端实例都有独立的全局变量空间,关终端也不清数据,理解错这点调试会绕大弯。

「用全局变量卡住 EA 的执行阶段」

把交易想法固化成 MQL5 程序时,最怕机器人跑到一半失联却看不出卡在哪个环节。给每个功能模块配一对全局变量当「开始 / 结束」开关,就能在 EA 被强删或断线后反推中断点。 一个模块化 EA 通常拆成开仓、平仓、追随止损三块。每块两个控制点,意味着一共要 6 个全局变量,例如 gvarOpen_start、gvarOpen_finish 分别对应开仓模块的开始与结束。主函数在 OnInit 之后于 Main() 里先把所有标志置 false,模块跑完再清自己的那一对。 实测里把示例 EA 挂上图表正常跑,再从图表移除,日志会留下「中断位于 Open() 模块」这类记录;按 F3 打开全局变量列表,只见 gvarOpen_start 被归零,其余还在。说明开仓指令执行失败能被立刻定位。同图表重启机器人,日志补发阶段故障报警——外汇与贵金属杠杆高,这类中断往往意味着滑点或无服务器应答,重跑前先确认 TERMINAL_CONNECTED。 下面这段是主模块骨架:OnInit 只回 INIT_SUCCEEDED;OnDeinit 清图表注释;OnTick 直接调 Main()。Main() 先循环把 6 个标志写 false,再三重判断交易权限与连接,通过后依次跑 Open()、Close()、Trail()。全局变量名数组写死 6 个字符串,和前面说的控制点一一对应。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   class="kw">return INIT_SUCCEEDED;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   Comment("");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   Main();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Main module                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Main(class="type">void)
  {
class=class="str">"cmt">//--- set flags for all modules
   for(class="type">int idx=class="num">0;idx<GVARS_LIST_SIZE;idx++)
      SetFlag(idx,false);
class=class="str">"cmt">//--- Check the trade possibility and connectivity
class=class="str">"cmt">//--- permission to trade
   if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      class=class="str">"cmt">//--- connection to the trading server
      if(TerminalInfoInteger(TERMINAL_CONNECTED))
         class=class="str">"cmt">//--- permission to trade for the launched EA
         if(MQLInfoInteger(MQL_TRADE_ALLOWED))
           {
            class=class="str">"cmt">//--- class="num">1) opening module
            Open();
            class=class="str">"cmt">//--- class="num">2) closing module
            Close();
            class=class="str">"cmt">//--- class="num">3) Trailing Stop module
            Trail();
           }
  }
class=class="str">"cmt">//--- global variables: names
class="type">class="kw">string gVar_names[class="num">6]=
  {
   "gvarOpen_start","gvarOpen_finish",
   "gvarClose_start","gvarClose_finish",
   "gvarTrail_start","gvarTrail_finish"
  };

开仓模块的阻塞式生命周期与全局标记

EA 里把开仓逻辑单独封进 Open() 函数,入口先打一行 Comment 显示当前模块名加函数名,方便在图表上实时看跑到哪一步。 函数体内先用 IsStopped() 拦一道,避免 EA 被手动停止后还硬跑。通过才清掉模块起始标记 SetFlag(0,true),随即 Sleep(1250) 原地停约 1.25 秒——这段时间主线程被占住,其他事件不处理,属于典型的阻塞式模块调度。 睡醒后置 SetFlag(1,true) 清掉结束标记,逻辑上假定本模块耗时就是 1.25 秒左右。下面是这段 Open 函数的原文: [CODE]

//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); } } [/CODE] 日志里能看出端倪:2014.10.22 20:14:29 曾出现 Program forced to terminate before execution: <<Open_finish>>,说明那次 EA 在 Sleep 没跑完就被强停,结束标记没写上。 到了 20:28:25.135 的终端记录里,Open_finish、Close_start、Close_finish、Trail_start、Trail_finish 全部 Non-zero value,意味着各模块全局标记都已置位,多模块衔接在那时是完整的。外汇与贵金属品种波动大、点差跳变频繁,这种靠 Sleep 硬等的模块在实盘可能错过入场窗口,建议在 MT5 用 EURUSD.e H1 复刻这段,观察强制停止后标记残留情况。

MQL5 / C++
class=class="str">"cmt">//| Open module                                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Open(class="type">void)
  {
   Comment(curr_module+__FUNCTION__);
class=class="str">"cmt">//---
   if(!IsStopped())
     {
      class=class="str">"cmt">//--- clear the module start flag
      SetFlag(class="num">0,true);
      class=class="str">"cmt">//--- assume that the module operates for approximately class="num">1.25 s
        {
         Sleep(class="num">1250);
        }
      class=class="str">"cmt">//--- clear the module finish flag
      SetFlag(class="num">1,true);
     }
  }

◍ 全局变量落盘的两个致命写法

把终端全局变量当成跨程序控制点,思路本身没问题:用 CGlobalVarList 的 Save/Load 把变量序列到 txt,就能在多个 MT 实例间共享状态。但代码里两处写法会直接让文件写出 0 KB 并抛「创建指针错误」。 第一处是空指针判断写反了: CGlobalVarList *list = new CGlobalVarList; if(list!=NULL){ Print("Pointer Create Error"); } new 成功时 list 不是 NULL,此时反而打了错误日志,而真正分配失败时没拦住,后面 Save 直接崩。 第二处是 Save 的句柄用法:FileOpen 返回的是 int 句柄,而 CGlobalVarList::Save 期望的是 FILE 句柄对象(CFile 之类),把 int 硬塞进去,list.Save(hFile) 必然返回 false,于是文件建了却是 0 字节。 实测在 MT4 公共目录 C:\Users\<名>\AppData\Roaming\MetaQuotes\Terminal\Common\Files 下,Write.txt 生成大小确为 0 KB,Read.txt 同理因 Load 失败报「指针创建错误」。外汇与贵金属交易本身高风险,这类全局变量共享方案只建议用于状态同步,切勿拿来直接下单控仓。 想验证的话,把指针判断改成 if(list==NULL) 并在 Save 前用 CFile 封装句柄,重新挂脚本到图表,文件大小应从 0 KB 变成实际字节数。

MQL5 / C++
class=class="str">"cmt">// WriteGlobalParameters.mq4 核心片段
CGlobalVarList *list = new CGlobalVarList;      class=class="str">"cmt">// 新建全局变量列表对象
if(list!=NULL){ Print("Pointer Create Error"); } class=class="str">"cmt">// 判错写反:成功才打错误
class="type">int hFile;                                       class=class="str">"cmt">// 声明 class="type">int 文件句柄
hFile=FileOpen(file,FILE_WRITE|FILE_COMMON);     class=class="str">"cmt">// 打开公共目录文件,返回 class="type">int
if(hFile>=class="num">0){
  if(!list.Save(hFile)){ Print("File Save Error"); } class=class="str">"cmt">// Save 需 CFile 句柄而非 class="type">int,必败
  FileClose(hFile);
}
class="kw">delete list;

「把这条线请下神坛」

实测里,即便把 ReadGlobalParameters.mq4 挂到图表上,终端弹出的报错和全局变量列表依旧是空的,和前面第①类现象并无二致。 我们额外用 FileOpen() 默认的 FILE_CSV、FILE_UNICODE、FILE_BIN、FILE_ANSI 四种标志位重复了同一套实验,结果没有任何变化,说明问题不在文件访问模式,而在全局变量本身的数据形态。

  • 年有用户指出 MT 全局变量只能是 double 类型,想要 key:value 的 CMap 或更灵活的内存指针方案并未落地。开 MT4/MT5 自己跑一遍这段空列表现象,就能确认:别把全局变量当成万能跨 EA 存储,它只是个双精度数的窄通道。
把跨程序状态交给小布盯盘看
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到全局变量存活与脚本写入痕迹,你专注决策而不是翻日志。

常见问题

会,全局变量生存期不随终端关闭停止,除非显式删除或设为临时变量并在会话结束清除。
MQL 只能通过参数传类型,模板方法借假参数推导返回类型,从而拿到调用方指定的数据类型值。
不会,每个终端有独立全局变量内存空间,命名空间隔离,不会跨终端重叠。
可以,小布盯盘对应品种页已集成终端全局变量存活与脚本写入的诊断视图,省去手动按 F3 查列表。
临时变量在终端会话结束或显式清除时销毁,普通变量持续保留,适合做跨程序短生命周期信号传递。