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

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

第 1/3 篇

「在 MT5 里直接抓网页数据」

很多交易者以为 EA 只能吃本地行情,其实 MetaTrader 5 自带的 WebRequest 就能让智能交易系统直接向外部接口要数据。2022 年 8 月那篇官方示例里,一个基础请求模板在测试环境跑了 1546 次调用,说明这套机制在实盘前先用策略测试器压一遍是可行的。 不过要注意:外汇和贵金属本身杠杆高、波动快,EA 一旦依赖外网延迟,滑点可能被放大,任何基于外部数据的信号都只是概率倾向,不是确定结果。 开 MT5 验证时,先到「工具—选项—EA 交易」里勾选「允许 WebRequest 访问」,并把目标域名加进白名单,否则代码跑起来会直接返回空。这一步漏了,后面全白费。

抓到数据只是第一步

前一篇讲清楚了 MT5 怎么从专业站点拉取带标签的数据,以及检索和解析背后的逻辑。但把网页内容塞进终端,本身不产生任何交易价值。 真正麻烦的是:怎么把抓到的结构化信息丢进 EA 的决策流里。MT5 原生给了一批网络与缓存函数,不会用这几个接口,你只能停在「能看到数据」这一步。 实际验证时,打开 MT5 的 MetaEditor,搜 WebRequest 和 ArrayResize 的用法,先确认你的 EA 能拿到响应体再谈后续。外汇与贵金属杠杆高,任何自动读取外部数据的逻辑都可能因延迟或站点改版失效,需自行压测。

◍ 从概念到 MT5 里的具体落地

这一节承接前一篇的框架,直接进 MetaTrader 5 实操层。前一篇若没读透,这里的调用链会断——很多功能在平台里藏得深,官方文档很少把「某个 API 能这么用」讲清楚。 我们这次偏要用那些不常被碰的接口,把之前抽象的思路转成可跑的代码。MT5 终端里至少 30% 的内置资源,普通 EA 模板根本不会去调。 先别急着写策略逻辑,把环境跑通、把非常规接口摸熟,后面才有东西可验证。

「用定时器把 Web 请求踢出主线程」

EA 直接在主循环里等 Web 服务器响应,是最危险的写法。哪怕只卡住几百毫秒,也可能让平台交易系统连同 EA 自身一起陷入停顿,外汇和贵金属这种高波动品种上风险尤其突出。 原文给出的改进思路是利用 MT5 的定时器线程:用 EventSetTimer(1) 让 OnTimer 每秒触发一次,WebRequest 调用被隔离在定时器里,主线程的 OnTick 照常跑行情判断。这样远程服务器慢一点,也不会直接拖死 EA。 下面这段代码就是该思路的最小可运行骨架。OnInit 里挂 1 秒定时器,OnDeinit 里销毁它,OnTimer 中调用 GetDataURL 去抓 tradingeconomics.com/stocks 页面里 IND:IND 附近的一段文本,超时设 100 毫秒。 [CODE] #property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); // 设置定时器,每 1 秒触发一次 OnTimer return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); // 销毁定时器,避免 EA 退出后残留 } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { // 每秒向指定 URL 发请求,提取页面中 IND:IND 标记附近的数据 Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "IND:IND", 172783, 173474, 0x0D)); } //+------------------------------------------------------------------+ string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit) { string headers, szInfo = ""; char post[], charResultPage[]; int counter; // 发起 GET 请求,超时 100ms;失败返回 "Bad" if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad"; // 校验响应头是否以 <!doctype html> 开头(偏移 2 处比对) for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed"; // 在偏移 172783 处查找 "IND:IND" 锚点,不匹配返回 "Error" for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error"; // 跳过 iInfo 偏移后的空格(0x20) for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++); // 从有效数据起,读到换行符 0x0D 为止,逐字符拼成字符串 for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]); return szInfo; } //+------------------------------------------------------------------+ [/CODE] 打开 MT5 新建 EA,把上面代码整段贴进编辑器,编译后挂到任意图表,终端里就会每秒打印一次抓到的字段。若你本地有转发服务器,把 url 换成内网地址、timeout 调到 10 左右,响应会快一个数量级,主线程被拖住的概率也更低。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property version "class="num">1.00"
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()
{
    Print(GetDataURL("https:class=class="str">"cmt">//tradingeconomics.com/stocks", class="num">100, "<!doctype html>", class="num">2, "IND:IND", class="num">172783, class="num">173474, 0x0D));
}
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); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) class="kw">return "Failed";
    for (class="type">int c0 = class="num">0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) class="kw">return "Error";
    for (counter = class="num">0; charResultPage[counter + iInfo] == 0x20; counter++);
    for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
    
    class="kw">return szInfo;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

用平台内置的Web请求函数定时拉取页面,解析返回文本里的关键字段即可,避免在主线程卡顿。
网络调用阻塞了主线程。用定时器把请求丢到独立节拍里执行,主循环只管交易逻辑。
可以。小布盯盘的AIGC能直接读外源数据并生成品种页诊断,你打开对应页面就能看汇总。
按固定分隔符或标签做字符串截取,先打印样本确认结构,再写循环提取目标字段。
大概率会触发限制。建议间隔放长到数十秒以上,并加失败重试与超时退出保护。