MQL5 编程基础: 终端的全局变量·进阶篇
(2/3)· 新手常误以为变量随终端关闭消失,其实全局变量能跨会话存活并贯通 EA、脚本与指标
全局变量的类型精度与批量创建耗时
在 MT5 脚本里用 CGlobalVar 类操作终端全局变量,能直接看到不同数据类型落盘后的精度差异。上面日志里 Double value 存成了 3.123456789101235,而 Float value 只剩 3.1234567,String 虽保留长串数字但本质是文本,后续参与数值运算前得先转型。 临时全局变量(Is temporary: 1)在终端退出时会被清除,适合做脚本间一次性传参;若去掉临时标记,变量会驻留到手动删除。重设数值时日志显示 New value is set: 3.191000,条件分支里又落到 3.180000,说明写值逻辑可按运行时状态分流。 下面这段脚本实测批量建 10000 个全局变量:循环里每个变量名用 Test_var+序号,初始值取 idx+0.15,标记临时为 true。 [CODE] #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); } [/CODE] 代码逐行拆: #property script_show_inputs —— 让脚本输入参数在运行前可改; #include "CGlobalVar.mqh" —— 引入全局变量封装类; input uint InpCnt=10000 —— 外部输入,默认建 1 万个; void OnStart() —— 脚本入口; uint start=GetTickCount() —— 记下起始毫秒数; for 循环从 0 到 InpCnt-1 —— 逐个构造 CGlobalVar 对象; gVar.Create("Test_var"+序号, idx+0.15, true) —— 建名为 Test_varN 的临时全局变量,初值带 0.15 偏移; 若 Create 失败弹 Alert; 循环后 GetTickCount()-start 得总耗时; PrintFormat 把“创建 N 个全局变量耗时 X 毫秒”打到日志。 外汇与贵金属交易高风险,全局变量仅作本地计算缓存,不代表任何方向判断。你在 AUDUSD.e 的 H1 图表挂这段脚本,终端日志会返回具体 ms 数,机器越快数值越低,可据此评估高频建变量对主线程的占用。
class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- class="macro">#include "CGlobalVar.mqh" input class="type">uint InpCnt=class="num">10000; class=class="str">"cmt">// Number of variables class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- start value class="type">uint start=GetTickCount(); class=class="str">"cmt">//--- for(class="type">uint idx=class="num">0;idx<InpCnt;idx++) { CGlobalVar gVar; class=class="str">"cmt">//--- Create a temporary global var if(!gVar.Create("Test_var"+IntegerToString(idx+class="num">1),idx+class="num">0.15,true)) Alert("Error creating a global variable!"); } class=class="str">"cmt">//--- finish value class="type">uint time=GetTickCount()-start; class=class="str">"cmt">//--- to print PrintFormat("Creation of %d global variables took %d ms",InpCnt,time); }
◍ 全局变量清单类的坑与改法
在 MT5 里批量管全局变量,常见做法是继承标准 CList 写一个 CGlobalVarList。它内部用 m_gvars_type(ENUM_GVARS_TYPE 枚举)筛类型,再靠 LoadCurrentGlobals 把终端里现有的全局变量读进链表。 原版 LoadCurrentGlobals 在循环里直接调了 CGlobalVar::Create,副作用是:脚本一跑,终端会凭空多出几个黄色高亮的全局变量,原有变量索引也被打乱。日志里能看到非预期的创建记录,这就是混乱来源。 改用低活性做法更稳:给 CGlobalVar 加一个带参构造函数,LoadCurrentGlobals 里只做 new CGlobalVar(name,val,time) 并挂进链表,不主动建变量。改完再跑 Globals_test3.mq5,打印就干净了。 实测中我们建了 10 个多元全局变量,链表 gvarList 只收完整的那些,随后删除,Expert 日志确认仅含正确创建的完全变量;清掉后终端剩 5 个临时变量。CGlobalVarList 还实现了 Save/Load 到文件的方法,方便跨会话落盘。外汇与贵金属全局变量共享终端内存,多 EA 并发改写有高风险,验证时建议用独立测试账户。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CGlobalVarList | class=class="str">"cmt">//+------------------------------------------------------------------+ class CGlobalVarList : class="kw">public CList { class=class="str">"cmt">//--- === Data members === --- class="kw">private: ENUM_GVARS_TYPE m_gvars_type; class=class="str">"cmt">//--- === Methods === --- class="kw">public: class=class="str">"cmt">//--- constructor/destructor class="type">void CGlobalVarList(class="type">void); class="type">void ~CGlobalVarList(class="type">void){}; class=class="str">"cmt">//--- load/unload class="type">bool LoadCurrentGlobals(class="type">void); class="type">bool KillCurrentGlobals(class="type">void); class=class="str">"cmt">//--- working with files class="kw">virtual class="type">bool Save(const class="type">int _file_ha); class="kw">virtual class="type">bool Load(const class="type">int _file_ha); class=class="str">"cmt">//--- service class="type">void Print(const class="type">int _digs); class="type">void SetGvarType(const ENUM_GVARS_TYPE _gvar_type); class=class="str">"cmt">//--- class="kw">private: class="type">bool CheckGlobalVar(const class="type">class="kw">string _var_name); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Load current global vars | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CGlobalVarList::LoadCurrentGlobals(class="type">void) { ENUM_GVARS_TYPE curr_gvar_type=this.m_gvars_type; class="type">int gvars_cnt=GlobalVariablesTotal(); class=class="str">"cmt">//--- for(class="type">int idx=class="num">0;idx<gvars_cnt;idx++) { class="type">class="kw">string gvar_name=GlobalVariableName(idx); if(this.CheckGlobalVar(gvar_name)) class="kw">continue; class=class="str">"cmt">//--- gvar properties class="type">class="kw">double gvar_val=GlobalVariableGet(gvar_name); class="type">class="kw">datetime gvar_time=GlobalVariableTime(gvar_name); CGlobalVar *ptr_gvar=new CGlobalVar(gvar_name,gvar_val,gvar_time); class=class="str">"cmt">//--- control gvar type
「按类型筛全局变量时先查指针」
遍历终端全局变量时,先确认指针是否为动态分配再决定后续操作,否则直接返回 false 会中断整轮加载。下面这段过滤逻辑在 ptr_gvar 为 POINTER_DYNAMIC 时才进入分支,用 curr_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; } 枚举 ENUM_GVARS_TYPE 给筛选定了三个档:GVARS_TYPE_ALL=-1 收全部,GVARS_TYPE_FULL=0 只正式,GVARS_TYPE_TEMP=1 只临时。OnStart 里调 LoadCurrentGlobals 后打印数量,2014.10.21 11:35 在 AUDUSD.e H1 上跑出 'Number of variables in the list: 10',说明当时终端里挂了 10 个全局变量。 创建单个变量用 ptr_gvar.Create(gvar_name,gvar_val),LoadCurrentGlobals 内部先拿 GlobalVariablesTotal 计数,再循环 new CGlobalVar 把名称、值、时间塞进去。开 MT5 建个脚本引 CGlobalVarList.mqh,改 GVARS_TYPE 参数就能验证不同筛选下的变量总数。
if(CheckPointer(ptr_gvar)==POINTER_DYNAMIC) { if(curr_gvar_type>GVARS_TYPE_ALL) { class="type">bool is_temp=ptr_gvar.IsTemporary(); class=class="str">"cmt">//--- only full-fledged if(curr_gvar_type==GVARS_TYPE_FULL) {if(is_temp)class="kw">continue;} class=class="str">"cmt">//--- only temporary else if(curr_gvar_type==GVARS_TYPE_TEMP) {if(!is_temp)class="kw">continue;} } class=class="str">"cmt">//--- try to add if(this.Add(ptr_gvar)>-class="num">1) class="kw">continue; } class=class="str">"cmt">//--- class="kw">return false; } class=class="str">"cmt">//--- class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Enumeration for gvars type | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_GVARS_TYPE { GVARS_TYPE_ALL=-class="num">1, class=class="str">"cmt">// all global GVARS_TYPE_FULL=class="num">0, class=class="str">"cmt">// only full GVARS_TYPE_TEMP=class="num">1, class=class="str">"cmt">// only temporary }; class="macro">#include "CGlobalVarList.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CGlobalVarList gvarList; gvarList.LoadCurrentGlobals(); PrintFormat("Number of variables in the set: %d",gvarList.Total()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Load current global vars | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CGlobalVarList::LoadCurrentGlobals(class="type">void) { class="type">int gvars_cnt=GlobalVariablesTotal(); class=class="str">"cmt">//--- for(class="type">int idx=class="num">0;idx<gvars_cnt;idx++) { class="type">class="kw">string gvar_name=GlobalVariableName(idx); class="type">class="kw">double gvar_val=GlobalVariableGet(gvar_name); class="type">class="kw">datetime gvar_time=GlobalVariableTime(gvar_name); CGlobalVar *ptr_gvar=new CGlobalVar(gvar_name,gvar_val,gvar_time);
用类封装清理全局变量
在 MT5 里全局变量(gvar)跨 EA / 脚本存活,手动逐个删容易漏。把增删查收进一个 CGlobalVarList 类,脚本只管调接口,逻辑清爽得多。 下面这段是类内部 Add 时的指针校验:动态指针才进表,插入失败直接返 false,整体成功才返 true。 [CODE] if(CheckPointer(ptr_gvar)==POINTER_DYNAMIC) if(this.Add(ptr_gvar)>-1) continue; //--- return false; } //--- return true; } [/CODE] 实盘前先跑一遍测试脚本:设 InpGvarType=GVARS_TYPE_FULL,加载当前 gvar 后打印、再 KillCurrentGlobals 打印。某次 AUDUSD.e H1 回测里,删除前列表有 10 个全局变量、当前类内挂了 5 个(gVar10_prog_test1_scr 值 16.64、gVar2_prog_test1_scr 值 4.64),删完列表清空。 外汇 / 贵金属杠杆高,全局变量若没清干净,下次脚本可能读到上回残留的数值,信号会偏。开 MT5 把这类封装脚本挂上,先在小周期验证删除逻辑再上真仓。
if(CheckPointer(ptr_gvar)==POINTER_DYNAMIC) if(this.Add(ptr_gvar)>-class="num">1) class="kw">continue; class=class="str">"cmt">//--- class="kw">return false; } class=class="str">"cmt">//--- class="kw">return true; }
◍ 删完变量后本地列表归零的日志实况
在 AUDUSD.e 的 H1 图表上跑 Globals_test3,11:05:01.613 这条时间戳里还能看到三个全局变量挂在程序作用域:gVar4_prog_test1_scr 值为 7.64,gVar6 为 10.64,gVar8 为 13.64,编号分别是 #3 到 #5。 等到 11:06:18.675 打出「Print the list after deletion」之后,下一秒的日志直接显示本地列表空了。Global variable type 仍是 GVARS_TYPE_FULL,但当前列表里的变量数变成 0,而终端全局变量总数还报 5——说明删的是脚本持有的引用,不是终端全局池。 这种差异在排查 EA 残留全局变量时很实用:若你以为清掉了其实只清了本地列表,下次加载还可能读到旧值。外汇与贵金属交易用全局变量跨 EA 通信本就高风险,建议每次退出前用 GlobalVariableDelete 显式销账。
GR class="num">0 class="num">11:class="num">05:class="num">01.613 Globals_test3(AUDUSD.e,H1) Gvar #class="num">3, name - gVar4_prog_test1_scr, value - class="num">7.6400000000 RD class="num">0 class="num">11:class="num">05:class="num">01.613 Globals_test3(AUDUSD.e,H1) Gvar #class="num">4, name - gVar6_prog_test1_scr, value - class="num">10.6400000000 LJ class="num">0 class="num">11:class="num">05:class="num">01.613 Globals_test3(AUDUSD.e,H1) Gvar #class="num">5, name - gVar8_prog_test1_scr, value - class="num">13.6400000000 EH class="num">0 class="num">11:class="num">06:class="num">18.675 Globals_test3(AUDUSD.e,H1) Print the list after deletion. FS class="num">0 class="num">11:class="num">06:class="num">19.003 Globals_test3(AUDUSD.e,H1) JJ class="num">0 class="num">11:class="num">06:class="num">19.003 Globals_test3(AUDUSD.e,H1) ---===Local list===--- HN class="num">0 class="num">11:class="num">06:class="num">19.003 Globals_test3(AUDUSD.e,H1) Global variable type: GVARS_TYPE_FULL KH class="num">0 class="num">11:class="num">06:class="num">19.003 Globals_test3(AUDUSD.e,H1) Total number of global variables: class="num">5 QP class="num">0 class="num">11:class="num">06:class="num">19.003 Globals_test3(AUDUSD.e,H1) Number of global variables in the current list: class="num">0