从头开始开发智能交易系统(第 17 部分):访问 web 上的数据(III)(基础篇)
📘

从头开始开发智能交易系统(第 17 部分):访问 web 上的数据(III)(基础篇)

第 1/3 篇

「用 MQL5 抓取网页 JSON 的底层套路」

在 MT5 里做策略外数据增强,绕不开从 web 拉取 JSON。MQL5 自 build 2875 起内置了 WebRequest,但默认禁止外部域名,需要在 EA 设置的「允许 WebRequest 的 URL 列表」里手动加白,否则运行时直接返回 520 错误码。 下面这段演示了如何向一个公开汇率接口发 GET 请求并读出字段。注意 User-Agent 头不能省,部分网关会拦截空 UA 导致 403。 实际跑下来,本地回测环境若勾选了「离线模式」会令 WebRequest 失效;实盘或实时模拟盘才真能出数。一次同步耗时通常在 80–300 ms,高频 tick 内调用会明显拖慢执行,建议放在 OnTimer 而非 OnTick。

MQL5 / C++
class="type">class="kw">string headers="";
class="type">class="kw">string result;
class="type">char data[];
class="type">int timeout=class="num">5000;
class="type">class="kw">string url="https:class=class="str">"cmt">//api.exchangerate.host/latest?base=USD";
if(WebRequest("GET",url,headers,timeout,data,result)==class="num">200)
{
   Print("HTTP class="num">200, 返回长度=",ArraySize(data));
}
else
{
   Print("请求失败, 错误码=",GetLastError());
}

◍ 三种 web 数据通道的取舍

上篇里我们实打了三种把 web 数据喂给 EA 的路子,各有硬伤。第一种让 EA 直接抓远程页面,服务器一慢 EA 就卡在等待响应上,实盘里这种阻塞可能直接漏掉信号。 第二种用脚本当服务器、EA 当客户端、对象当信道,不阻塞是真好用,但一切时间帧就崩——你换周期对象重连的麻烦够喝一壶。 第三种拿服务配合全局变量,时间帧问题没了,却撞上 MT5 全局变量只认 double 这条铁律。不少人图省事直接塞文本进通道,本质是在绕系统设计的本意。 本篇要做的,是绕开 double 限制另写一套替代系统,但先说透:这不是点几下就能跑的活,得自己撸不少代码。

用双精度变量塞进 8 字节通道

MT5 的全局变量通道只认 double 类型,而 double 在底层是 8 字节的 QWORD。很多交易者忘了计算机按字节寻址:1 字节 = 8 位,布尔型仅占 1 位,所以一个字节里能挤下 8 个 bool。把字节拼起来,2 字节叫 WORD,4 字节叫 DWORD,8 字节叫 QWORD——这套命名来自汇编,MQL5 的有符号整数和浮点都落在这条谱系上。 具体映射很直接:char 占 1 字节,short 占 2 字节(WORD),int 占 4 字节(DWORD),long 和 double 都占 8 字节(QWORD)。float 也是 4 字节 DWORD。也就是说,一个 double 全局变量最多能原样容纳 8 个可打印字符——这是 EA 与本地服务之间做极低延迟通信的入口。 实际用法上,若消息不超过 8 个可打印字符,单次写一个全局变量即可完成传递;超过就要拆块,用多个全局变量在一个 tick 内并发写出,接收端再拼回原文。外汇与贵金属市场高杠杆、滑点频繁,这类通道只适合单机低延迟辅助,别拿它当跨网络可靠总线。 真要传长消息,可以参考 TCP/IP 或 UDP 的分段重传思路,在全局变量层自己建模“块序号 + 等待下一帧”的协议。不必从零造轮子,理解协议逻辑后,难点就只剩 MQL5 代码实现本身。

「用 union 在全局变量里塞进 8 字节信道」

MT5 的终端全局变量只能收发 double 类型,物理上固定占 8 字节。想让 EA 和服务之间传自定义字节流,直接声明 double 会丢掉字符布局的灵活性,这里改用 union 共享同一段存储:double value 和 char Info[sizeof(double)] 重叠,写字符实际落在双精度的 8 字节内部。 头文件里用 sizeof(double) 取长度,而不是硬写 8。双精度在现行平台就是 8 字节,若将来加长,这段声明会自动适配,不用手动改数组大小。 服务端是 service 程序,在 OnStart 里跑 while 循环不断写全局变量;客户端 EA 用 EventSetTimer(1) 每秒触发 OnTimer,检查 InnerChannel 是否存在,存在就 GlobalVariableGet 读进 loc.value,再用 CharArrayToString(loc.Info, 0, sizeof(uDataServer)) 把字节翻成字符串打印。注意 MQL5 的 string 沿 C/C++ 规则,扩展字符码不能直接用,要自创编码或做数据压缩才能突破 8 字节限制。 别把正态当圣经:这套内部信道本身不复杂,但把字符塞进 double 的思路能衍生出很多非常规通信玩法,外汇与贵金属交易自带高杠杆高风险,拿去传信号前先在策略测试器里跑通再上实盘。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_GlobalNameChannel   "InnerChannel"
class=class="str">"cmt">//+------------------------------------------------------------------+
union uDataServer
{
      class="type">class="kw">double  value;
      class="type">char    Info[class="kw">sizeof(class="type">class="kw">double)];
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property description "Testing internal channel\nvia terminal global variable"
class="macro">#class="kw">property version "class="num">1.04"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Inner Channel.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
        EventSetTimer(class="num">1);
        
        class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
        EventKillTimer();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
{
        uDataServer loc;
        
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
            GlobalVariableGet(def_GlobalNameChannel, loc.value);
            Print(CharArrayToString(loc.Info, class="num">0, class="kw">sizeof(uDataServer)));
        }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property service
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property version   "class="num">1.03"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Inner Channel.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
        uDataServer loc;
        class="type">char car = class="num">33;
        
        class="kw">while (!IsStopped())
        {

◍ 用全局变量做跨实例心跳通道

这段片段演示了 EA 之间借助全局变量互通状态的一种写法:先确认名为 def_GlobalNameChannel 的全局变量存在,不存在就用 GlobalVariableTemp 临时建一个,避免后续读写报空。 循环里把 uDataServer 数组逐个字节填成递增的 ASCII 字符,car 从某初值开始,到 127 后回绕到 33,相当于生成一段 33~127 的可打印字符流,塞进 loc.Info[] 再整体写回全局变量。 每次写完调 Sleep(1000) 挂起 1 秒,意味着这条通道的心跳周期就是 1 秒级;在 MT5 里打开全局变量窗口(工具→全局变量)能看到 value 每秒被刷新一次。外汇与贵金属杠杆高,这类跨 EA 通信若用于跟单或风控,要先在模拟盘验证时序竞争。

MQL5 / C++
if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
for (class="type">char c0 = class="num">0; c0 < class="kw">sizeof(uDataServer); c0++)
{
   loc.Info[c0] = car;
   car = (car >= class="num">127 ? class="num">33 : car + class="num">1);
}
GlobalVariableSet(def_GlobalNameChannel, loc.value);
Sleep(class="num">1000);

常见问题

用内置的 WebRequest 发 GET 请求拿返回文本,再手动用 StringToCharArray 和 JSON 解析函数拆字段,注意在设置里勾选允许访问的 URL 列表。
用全局变量做心跳通道最省事,键名带唯一前缀避免冲突,写后读即可互通,比文件 IO 延迟低且不用管路径权限。
可以,小布能按你设的品种页定时拉取并比对通道心跳,异常时直接标红提醒,不用自己写轮询代码。
用 union 把两个 double 映射到同一块 8 字节内存,或借助 long 类型全局变量做位重组,省去序列化开销。
实时性高选 WebSocket 类长连,偶发拉取用 HTTP GET,跨实例共享选全局变量;外汇贵金属波动快,通道超时记得留重试。