从头开始开发智能交易系统(第 16 部分):访问 web 上的数据(II)·进阶篇
(2/3)· 直接轮询会卡死交易线程?本篇拆解本地转发与全局变量的安全接法
用图表对象搭一条进程间信道
EA 想在线抓数据又不想被远程服务器响应卡住,可以借 MetaTrader 5 的对象机制做一条信道:脚本当服务端把外网值写进对象,EA 当客户端定时读对象,两边互不阻塞。 原理类似内存共享区——服务器往里写、客户端读同一块,进程间解耦但连通。下面这段头文件声明了共享对象名,并在 EA 和脚本间复用。 脚本方案有个硬伤:一切换时间帧,MT5 会强制关掉脚本(即便写了无限循环),得手动重挂;若忘了挂,EA 可能读到陈旧或错误值。临时补法是对象里记脚本最后启动时间,EA 据此判断信道是否还活着。 外汇与贵金属波动剧烈、杠杆风险高,这类信道只解决通信解耦,不替代任何风控逻辑,参数与挂脚本动作都需在 MT5 实盘前自测。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#include <Inner Channel.mqh> class="type">int OnInit() { EventSetTimer(class="num">1); class=class="str">"cmt">// 设1秒定时器,驱动OnTimer周期执行 class="kw">return INIT_SUCCEEDED; } class="type">void OnDeinit(class="kw">const class="type">int reason) { EventKillTimer(); class=class="str">"cmt">// 退出时杀掉定时器,避免残留 } class="type">void OnTick() { } class="type">void OnTimer() { Print(GetInfoInnerChannel()); class=class="str">"cmt">// 每秒打印信道内信息,验证EA能读到 } class=class="str">"cmt">// Canal Intra Process.mqh class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#define def_NameObjectChannel "Inner Channel Info WEB" class="type">void CreateInnerChannel(class="type">void) { class="type">long id; ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, class="num">0, class="num">0, class="num">0); class=class="str">"cmt">// 在当前图表建一个隐藏标签对象作为信道载体 ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE); class=class="str">"cmt">// 颜色设无,前台不可见 } class="type">void RemoveInnerChannel(class="type">void) {
「用内通道对象抓取网页报价流」
这段脚本把外部网页数据拉进 MT5 图表对象,核心靠 Inner Channel 这个自定义对象做载体。先声明三个内联函数:删除通道对象、写文本、读文本,分别对应 ObjectDelete / ObjectSetString / ObjectGetString,图表 ID 用 ChartID() 实时绑定。 主逻辑在 OnStart:建好通道后进入 while 循环,每 200 毫秒调一次 GetDataURL 抓 tradingeconomics.com/stocks 的 INDU:IND 字段,再把返回串写进通道文本。循环靠 IsStopped() 退出,最后 RemoveInnerChannel 清理。 GetDataURL 是脏活集中地:WebRequest 用 GET 拉页,超时 100 毫秒;随后用 szTest 和 szFind 做字节级校验,偏移量写死 172783 与 173474,不匹配直接返 Bad/Failed/Error。从 iInfo 起跳过空格(0x20),一直读到回车(0x0D)截断,拼出纯净数值串。 开 MT5 把 Inner Channel.mqh 放好,直接跑这段能验证:标普期货报价会以文本形式挂在图表上,外汇与贵金属同样高风险,外网延迟或结构改动都可能让解析返 Error。
ObjectDelete(ChartID(), def_NameObjectChannel); class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">void SetInfoInnerChannel(class="type">class="kw">string szArg) { ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">class="kw">string GetInfoInnerChannel(class="type">void) { class="kw">return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Inner Channel.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CreateInnerChannel(); while (!IsStopped()) { SetInfoInnerChannel(GetDataURL("https:class=class="str">"cmt">//tradingeconomics.com/stocks", class="num">100, "<!doctype html>", class="num">2, "INDU:IND", class="num">172783, class="num">173474, 0x0D)); Sleep(class="num">200); } RemoveInnerChannel(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string GetDataURL(class="kw">const class="type">class="kw">string url, class="kw">const class="type">int timeout, class="kw">const class="type">class="kw">string szTest, class="type">int iTest, class="kw">const class="type">class="kw">string szFind, class="type">int iPos, class="type">int iInfo, class="type">char cLimit) { class="type">class="kw">string headers, szInfo = ""; class="type">char post[], charResultPage[]; class="type">int counter; if (WebRequest("GET", url, NULL, NULL, timeout, post, class="num">0, charResultPage, headers) == -class="num">1) class="kw">return "Bad"; for (class="type">int c0 = class="num">0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) class="kw">return "Failed"; for (class="type">int c0 = class="num">0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) class="kw">return "Error"; for (counter = class="num">0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++); for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]); class="kw">return (_StopFlag ? "" : szInfo); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用服务绕开脚本的时间帧终止坑
脚本在切换时间帧时会被终端强制终止,这是 MT5 的机制限制。若你的逻辑需要在图表周期变动后继续存活,脚本这条路就走不通,必须换载体。 服务(Service)是终端级后台程序,不依附于具体图表,因此不会因时间帧切换而退出。代价是它拿不到图表上下文,只能靠全局资源或自定义事件与其他模块通信。 知道一种方案能跑,和知道它跑不起来时怎么办,同样重要。服务的可行性在于常驻,局限性在于隔离——要在两者之间找平衡,而不是盲目替换。 下图展示的常驻结构看似轻量,但底层涉及很少被触及的终端资源调度。后续会拆开细节,方便你在自己的 EA 或指标里复用这套常驻思路。
服务与EA通信的全局变量通道
在服务和 EA 之间建立共享通信点是个麻烦事。脚本里可以用对象,但服务无法这么做;试过外部变量,性能又达不到预期(具体瓶颈可查 MQL5 外部变量文档)。后来翻终端发现一条更轻的路:全局变量。 终端里手动加的全局变量只支持 double 类型,表面看是限制,其实 double 本质就是 8 字符的短字符串,用来在程序间传数值或短消息完全够用。这就解决了跨程序通信的第一道坎——不必靠 dll 也能建通道。 剩下的问题是程序内部能否自己建全局变量,而不是只能用终端里声明的那些。实测 MQL5 允许在 EA、脚本、指标、服务里通过代码访问和创建全局变量,所以纯 MQL5 方案可行,不用被迫写 DLL。外汇与贵金属交易本身高风险,这类底层通道只解决通信,不预示任何方向。