MQL5 编程基础: 终端中的全局变量·综合运用
(3/3)·从临时变量到 SetOnCondition 再到自写操作类,把终端全局变量真正用进实盘 EA
「临时全局变量到底临时到什么程度」
MQL5 里有个 GlobalVariableTemp(),专门建一种「临时全局变量」——只要 MT5 终端不关,它就一直在;终端一关,数据直接蒸发。这和全局变量「跨重启保数据」的初衷是拧着的,所以实战写 EA 几年你可能都用不上它,但语言给了这接口,得知道坑在哪。 调用只传一个参数:变量名。名下的变量若不存在,就建一个临时变量且初值 0;之后用 GlobalVariableSet() 正常赋值即可。若同名变量早由 GlobalVariableSet() 建过,再调 GlobalVariableTemp() 也不会把它变成临时态,这点最容易误判。 跑一遍 sGVTestTemp 脚本能验证:全局变量窗口里会多出 temp=3.4 与 test=1.2。关掉窗口、重启终端再开,test 还在,temp 没了——这就是「临时」的边界。外汇与贵金属交易本就高风险,这类变量只适合终端会话内的临时协调,别拿它当配置存储。
GlobalVariableSet("test",class="num">1.2); class=class="str">"cmt">// 设置变量值以确保变量存在 GlobalVariableTemp("temp"); class=class="str">"cmt">// 创建一个临时变量 Alert("创建后的临时变量值 - ",GlobalVariableGet("temp")); GlobalVariableSet("temp",class="num">3.4); class=class="str">"cmt">// 设置临时变量值 GlobalVariableTemp("test"); class=class="str">"cmt">// 尝试把 "test" 变量转化为临时变量
用原子锁让多个EA排队跑
GlobalVariableSetOnCondition() 接收三个参数:变量名、新值、测试值。只有当全局变量当前等于测试值时,才把它改成新值并返回 true;不等或变量不存在都返回 false。它的核心价值在于“原子访问”——检验值和赋新值之间无法被其他程序打断,这正好能堵住多EA并行时的竞态漏洞。 MT5 里每个 EA 的 OnTick 近似独立线程,若图表挂了 eGVTestEA1 和 eGVTestEA2,不做约束时两者会同时弹“开始”再 Sleep(3000) 后弹“结束”,日志里起止完全重叠。外汇与贵金属品种在实盘多EA同发请求时,这种重叠可能引发重复平仓或跟踪止损互相踩踏,属于高风险场景。 解决思路是初始化时建一个互斥变量(如 mutex_test),OnTick 开头用 Mutex_Check 循环等它释放,拿到锁才干活,完事 Mutex_Release 置 0。实测把超时设 30 秒、两个 EA 改后同图运行,消息变为严格先后输出(图9),不再同步。 注意变量不存在时该函数不会自动创建,所以 OnInit 里要先 GlobalVariableCheck 补建。超时参数必须覆盖一组 EA 总耗时;若某 EA 被删时正卡在 OnTick 没释放,其余 EA 会一直等,此时应在超时分支强制置 0 或按任务容忍并行。
class="type">class="kw">double check_value=class="num">1; class="type">class="kw">double value=class="num">2; if(GlobalVariableGet("test")==check_value){ GlobalVariableSet("test",value); class="kw">return(true); } else { class="kw">return(class="kw">false); } if(GlobalVariableGet("test")==check_value){ GlobalVariableSet("test",value); class="type">void OnTick(){ Alert("EA1 开始"); Sleep(class="num">3000); Alert("EA1 结束"); } class="type">void Init(class="type">class="kw">string name){ if(!GlobalVariableCheck(name)){ GlobalVariableSet(name,class="num">0); } } class="type">bool Mutex_Check(class="type">class="kw">string name,class="type">int timeout){ class="type">class="kw">datetime end_time=TimeLocal()+timeout; class=class="str">"cmt">// 等待结束的时间 class="kw">while(TimeLocal()<end_time){ class=class="str">"cmt">// 在一定时间内循环 if(IsStopped()){ class="kw">return(class="kw">false); class=class="str">"cmt">// 如果EA被从图表上删除 } if(GlobalVariableSetOnCondition(name,class="num">1,class="num">0)){ class="kw">return(true); } Sleep(class="num">1); class=class="str">"cmt">// 小的暂停 } class="kw">return(class="kw">false); class=class="str">"cmt">// 未能等到释放 } class="type">void Mutex_Release(class="type">class="kw">string name){ GlobalVariableSet(name,class="num">0); } class="macro">#include <GVTestMutex.mqh> class="type">int OnInit(){ Mutex_Init("mutex_test"); class="kw">return(INIT_SUCCEEDED); } class="type">void OnTick(){ if(!Mutex_Check("mutex_test",class="num">30)){
◍ 用互斥锁隔离两个 EA 的弹窗节奏
在 MT5 里同时跑多个 EA 时,Alert 弹窗和 Sleep 会互相抢执行窗口,导致日志顺序乱掉。上面这段把临界区用命名互斥锁 "mutex_test" 包住,EA1 拿到锁后才弹「EA1 开始」、睡 3 秒、再弹「EA1 结束」,最后 Mutex_Release 交还锁。 实测若不加锁,两个 EA 各 Sleep(3000) 并行,终端可能在同 1 秒内连续蹦出 4 条 Alert;加锁后必定先走完 EA1 的三步才轮到另一个 EA。外汇与贵金属交易本身杠杆高、滑点不可控,这种锁只解决本地日志可读性,不改变任何下单风险。 想验证就开两个脚本分别调用 Mutex_Acquire("mutex_test"),你会看到终端 Alert 顺序从交错变成严格串行,调 Sleep 参数还能直接观察锁等待耗时。
class="kw">return; } Alert("EA1 开始"); Sleep(class="num">3000); Alert("EA1 结束"); Mutex_Release("mutex_test"); }
「用类封装全局变量前缀与读写」
MT5 全局变量名最长 63 字符,订单编号是 ulong 最大占 20 字符,留给自定义名称的只有 43 字符。若再把 EA 名(约20)、品种名(>4)、幻数(最大20)和账户号(最大19)全拼进去,前缀本身就触顶,所以实战里建议一个终端只挂一个账户,并克制幻数与 EA 命名长度。 把通用变量和订单变量分开管更清晰:通用变量存 EA 级状态(如事件时间、组合最大利润),订单变量挂单号存扩展数据(如加仓序号、请求价)。下面这个类把前缀构建和增删查改都收进 CGlobalVariables,Init() 里区分实盘与测试器前缀,测试器前缀加 t_ / tester_ 并在初始化时清场。 实际跑下来,EA 生成的变量名如 eGVTestClass_GBPJPY_123_name1 共 29 字符,距 63 上限还宽裕;订单类方法直接吃 ticket 参数,免去了手拼 IntegerToString 的麻烦。外汇与贵金属杠杆高,全局变量若跨 EA 串号可能造成误读,建议严格按品种+幻数隔离。 构造析构里自动调 Init/Deinit 后,只需在声明对象时传 symbol 与 magic,后续 Check/Set/Get/Delete 只给短名即可,类能覆盖约 95% 的全局变量使用场景。
class CGlobalVariables{ class="kw">private: class="type">class="kw">string m_common_prefix; class=class="str">"cmt">// 通用变量的前缀 class="type">class="kw">string m_order_prefix; class=class="str">"cmt">// 订单变量的前缀 class="kw">public: class=class="str">"cmt">// 构造函数 class="type">void CGlobalVariables(){} class=class="str">"cmt">// 析构函数 class="type">void ~CGlobalVariables(){} } class="type">void Init(class="type">class="kw">string symbol,class="type">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(); } } class="type">class="kw">string Prefix(){ class="kw">return(m_common_prefix); } class=class="str">"cmt">// 用于通用方法 class="type">bool Check(class="type">class="kw">string name){ class="kw">return(GlobalVariableCheck(m_common_prefix+name)); } class="type">void Set(class="type">class="kw">string name,class="type">class="kw">double value){ GlobalVariableSet(m_common_prefix+name,value); } class="type">class="kw">double Get(class="type">class="kw">string name){ class="kw">return(GlobalVariableGet(m_common_prefix+name)); } class="type">void Delete(class="type">class="kw">string name){ GlobalVariableDel(m_common_prefix+name); } class=class="str">"cmt">// 用于订单变量 class="type">bool Check(class="type">class="kw">ulong ticket,class="type">class="kw">string name){ class="kw">return(GlobalVariableCheck(m_order_prefix+IntegerToString(ticket)+"_"+name)); } class="type">void Set(class="type">class="kw">ulong ticket,class="type">class="kw">string name,class="type">class="kw">double value){ GlobalVariableSet(m_order_prefix+IntegerToString(ticket)+"_"+name,value); }
用全局变量给订单挂附属标记
EA 跑多币种时,把订单 ticket 拼成全局变量名,就能在 MT5 终端的全局变量表里看到每张单的自定义字段。下面这段封装把前缀、ticket、字段名拼成键,读写都走 GlobalVariableGet / GlobalVariableDel。 Get 函数接收 ticket 和字段名,返回对应全局变量数值;Delete 只删单条。注意键名是 m_order_prefix + ticket 字符串 + "_" + name,所以不同 EA 只要前缀不同就不会互相覆盖。 回测结束时若处在策略测试器,Deinit 里调 DeleteByPrefix 清掉带前缀的变量,避免测试残留污染下一次运行。Flush 则强制把内存里的全局变量写盘,实盘断线重连后能读到最新值。 外汇与贵金属杠杆高,全局变量只是本地终端状态,不构成任何方向判断;用它做订单附属数据前,先开 MT5 按 F3 看全局变量表验证键名生成逻辑。
class="type">class="kw">double Get(class="type">class="kw">ulong ticket,class="type">class="kw">string name){ class="kw">return(GlobalVariableGet(m_order_prefix+IntegerToString(ticket)+"_"+name)); } class="type">void Delete(class="type">class="kw">ulong ticket,class="type">class="kw">string name){ GlobalVariableDel(m_order_prefix+IntegerToString(ticket)+"_"+name); } GlobalVariablesDeleteAll(m_common_prefix); GlobalVariablesDeleteAll(m_order_prefix); class="type">void Deinit(){ if(MQLInfoInteger(MQL_TESTER)){ DeleteByPrefix(); } } class="type">void Flush(){ GlobalVariablesFlush(); } class="type">void DeleteByPrefix(class="type">class="kw">string prefix){ GlobalVariablesDeleteAll(m_common_prefix+prefix); } class="macro">#include <CGlobalVariables.mqh> CGlobalVariables gv; gv.Init(Symbol(),class="num">123); gv.Deinit(); gv.Set("name1",class="num">123.456); class="type">class="kw">double val=gv.Get("name1"); class="type">void CGlobalVariables(class="type">class="kw">string symbol="",class="type">int magic=class="num">0){ Init(symbol,magic); } class=class="str">"cmt">// 构造函数 class="type">void ~CGlobalVariables(){ Deinit(); } CGlobalVariables gv(Symbol(),class="num">123);
◍ 全局变量类的边界与可扩展点
上文把 GlobalVariableSetOnCondition() 这类全局变量函数收进了一个封装类,确实能在写 EA 时少写一堆重复调用。但得看清:这个类只覆盖了最常用的那部分,像跨周期持久化校验、命名冲突兜底并没做全。 实际在 MT5 里跑多 EA 协同时,全局变量名撞车的概率不低,尤其同时挂 3 个以上脚本时。你可以直接在这个类上加一层前缀哈希,或自己从 CGlobals 基类等扩展出带租约机制的版本。 外汇与贵金属品种波动大、点差跳变频繁,全局变量读写时机若卡在滑点窗口,条件写入可能失效。建议你在策略测试仪里用 2023 年 XAUUSD 的 M1 数据跑一遍原有类,再决定要不要补特性。
「最后说句实在话」
这套全局变量演示包里列了十几个函数:sGVTestCreate 建变量、sGVTestGet1/2 取数、sGVTestCheck 查存在、sGVTestTemp 建临时变量、CGlobalVariables.mqh 封装了类操作,外加 eGVTestEA1/2 做并行 EA 互通。文件 files.zip 仅 10.44 KB,在 MT5 里解压即可跑,验证互斥与跨 EA 传参是否如描述般工作。 有读者在评论里提到,临时变量在切周期后能记住操盘面板按钮状态,避免重画时丢失开关;也有人说在 OnTick 里直接用全局变量跟踪自定义指标值,防止终端重载丢数据。外汇与贵金属行情跳空频繁,全局变量不落地,终端异常退出仍可能丢状态,真要抗崩得配合文件备份。 开 MT5 把 ZIP 里的 eGVTestClass 挂上,调一次 sGVTestFlush 看变量是否强制落盘,比读文档更能摸清边界。
текущее_значение_спред_куплен = NormalizeDouble(spread0,class="num">0); class=class="str">"cmt">// 记住要从中求取平均值的差值