在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库·进阶篇
(2/3)· 还在为 WebRequest 的报头硬编码和编码细节头疼?这篇把 Python 式 HTTP 调用搬进 MQL5
「抓一眼 MT5 回传的 HTTP 头与报文」
在 MT5 里跑 WebRequest 做外部接口验证时,专家日志会把请求与响应的原始行全部打印出来。上面这段是 XAUUSD 日线周期下的一次测试,时间戳停在 08:10:34.578,回传 Content-Length 为 619 字节,服务端标识为 gunicorn/19.9.0。 日志里能看到响应头带了 Access-Control-Allow-Origin: * 且 Access-Control-Allow-Credentials: true,这种组合在跨域鉴权场景下容易踩坑,本地回测通过不代表券商环境放行。 报文主体里 data 字段是 {"username":"omega","password":"secret123"},明码塞凭证只是测试桩行为;真接信号源时务必走加密通道,外汇与贵金属杠杆品种本身高风险,接口泄露可能直接放大仓位暴露。 打开 MT5 终端的 Experts 标签,过滤 Requests test (XAUUSD,D1) 就能复现同构日志,先确认自己 EA 的 WebRequest 头是否也被原样 dump 出来。
◍ 抓包看 MT5 发出的 POST 长什么样
在 XAUUSD 的 D1 图表上跑一次请求测试,终端在 08:10:34.578 打出了完整回显。这一刻我们能直接看到 MetaTrader 5 实际向外发出的 HTTP 头与 JSON 体,而不是凭文档猜测。 请求头里 User-Agent 写明是 MetaTrader 5 Terminal/5.5120,运行环境为 Windows NT 10.0.19045 x64;Host 指向 httpbin.org,Content-Type 为 application/json,Content-Length 为 43,说明这次 POST 的 body 正好 43 字节。 JSON 体部分只塞了两个字段:username 为 "omega",password 为 "secret123"。回显的 url 是 https://httpbin.org/post,origin 显示出口 IP 为 197.250.227.26,X-Amzn-Trace-Id 可用于在 aws 侧追踪这条链路。 黄金外汇品种通过 EA 对外发请求属于高风险操作,密钥明文出现在 body 里意味着任何能抓包的中间节点都能拿到。开 MT5 把这段测试跑一遍,比对你的终端版本号与头字段,确认本地发出的报文和这里一致再谈下一步封装。
用 POST 把表单数据丢给外部接口
想在 MT5 脚本里和网页后端打交道,requests.mqh 的 CSession::post 是最直接的入口。下面这段把模拟登录表单以 application/x-www-form-urlencoded 方式发到 httpbin.org/post,超时设 5000 毫秒、不跟随重定向(false),跑完把响应全打印出来。 测试日志显示:状态码 200、reason 为 OK、response.ok 为 true,说明请求被服务端正常接收;Elapsed Time 约 1547 毫秒,即一次外网 POST 往返在 1.5 秒量级,实盘若嵌在 OnTick 里要警惕阻塞。 回传头里 Content-Type 是 application/json,证明 httpbin 把提交内容原样包成 JSON 返回,response.json["url"] 能直接取到提交地址。外汇与贵金属行情受杠杆与跳空影响,外接 API 延迟可能放大滑点风险,这类网络调用只适合离线的 D1 级分析脚本,别挂进高频执行流。
class="macro">#include <requests.mqh> CSession requests; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- class="type">class="kw">string form_data = "username=omega&password=secret123"; class=class="str">"cmt">//mimicking how the final form data is collected in the web CResponse response = CSession::post("https:class=class="str">"cmt">//httpbin.org/post", form_data, "Content-Type: application/x-www-form-urlencoded", class="num">5000, false); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (class="type">class="kw">string)response.ok); Print("Elapsed Time(ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }
「抓包看 EA 发往测试服务的表单字段」
在 MT5 专家日志里跑一次 XAUUSD 的 D1 周期请求测试,08:20:01.411 这条记录能完整还原客户端到服务端的握手过程。日志前缀 MD / PM / OK / HS 分别对应请求体长度、连接保活、服务端 gunicorn 19.9.0 版本与跨域头,Content-Length 固定为 587 字节。 翻到 RL 与 RE 两段高亮行,服务端回显的 form 字段暴露了 EA 实际 POST 的内容:password 为 secret123,username 为 omega。这说明脚本把凭证明文塞进表单体,而非放在 headers 里。 黄金外汇这类品种波动剧烈、杠杆风险高,任何把口令写死在 EA 里的做法都可能让实盘账户在本地被嗅探时直接暴露。开 MT5 把这段测试请求复跑一遍,核对你自己 EA 的 form 回显,别等真金白银的账户被拖库才回头查日志。
◍ 抓包看 MT5 发出的 HTTP 头
在 XAUUSD 的 D1 图表上跑一个 POST 请求测试,终端会在日志里原样吐出请求头与回显体。上面这段抓包记录发生在 08:20:01.411,目标主机是 httpbin.org,回显的 origin 字段为 197.250.227.26,说明请求确实从本机出口 IP 发出。 注意 Content-Type 被终端默认设为 application/x-www-form-urlencoded,而回显里的 json 字段为 null——这意味着如果你指望服务端直接解析成 JSON,在没显式改头之前会落空。User-Agent 显示为 MetaTrader 5 Terminal/5.5120,部分 API 网关会据此限流或拒绝,实盘前先拿 httpbin 自测一次最稳。 外汇与贵金属品种走这种外部请求存在高风险:网络抖动、券商禁出网都可能让策略在关键时刻失联,建议只在信号辅助层用,别绑死下单链路。
LP class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "Accept-Encoding": "gzip, deflate", CF class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "Accept-Language": "en;q=class="num">0.5", EK class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "Content-Length": "class="num">33", EL class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "Content-Type": "application/x-www-form-urlencoded", PH class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "Host": "httpbin.org", GO class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "User-Agent": "MetaTrader class="num">5 Terminal/class="num">5.5120 (Windows NT class="num">10.0.class="num">19045; x64)", FQ class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "X-Amzn-Trace-Id": "Root=class="num">1-class="num">68676482-6439a0a071c275773681a193" LM class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) }, JE class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "json": null, LM class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "origin": "class="num">197.250.class="num">227.26", KH class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) "url": "https:class=class="str">"cmt">//httpbin.org/post" LN class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) } IK class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) CO class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) JSON: NK class="num">0 class="num">08:class="num">20:class="num">01.411 Requests test(XAUUSD,D1) https:class=class="str">"cmt">//httpbin.org/post
在 MT5 里把 GET 拆成独立函数
上一节那个通用的 request 函数可以直接包一层,变成语义更清楚的 get、post、put、patch、delete。实际写 EA 或脚本时,只调 get 就不会误带 body,可读性比每次手写 request("GET", ...) 高得多。 get 函数本身不发送任何数据负载,仅用于拉取服务端信息。注意一个坑:即便你在 headers 里手动塞了 Content-type,也管不了服务器回什么格式——返回类型由对端决定,解析前得自己判断 response.headers。 下面这段是 get 的静态封装加一个实测调用。在 XAUUSD 的 D1 周期脚本里跑 https://httpbin.org/get,日志落出 Status Code: 200、Reason: OK,整轮耗时 Elapsed Time (ms): 1782,也就是约 1.8 秒完成一次外网 GET。外汇与贵金属行情接口若走类似外呼,网络抖动会让延迟浮动,属高风险链路,参数 timeout 默认 5000 毫秒留了余量。 把代码复制到 MT5 的脚本里,改一下 URL 就能验证你自己的 REST 接口通不通;若返回非 200,优先查本地防火墙而非代码逻辑。
class="kw">static CResponse get(class="kw">const class="type">class="kw">string url, class="kw">const class="type">class="kw">string headers = "", class="kw">const class="type">int timeout = class="num">5000) { class="kw">return request("GET", url, "", headers, timeout, false); } class="macro">#include <requests.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- CResponse response = CSession::get("https:class=class="str">"cmt">//httpbin.org/get"); Print("Status Code: ", response.status_code); Print("Reason: ", response.reason); Print("URL: ", response.url); Print("OK: ", (class="type">class="kw">string)response.ok); Print("Elapsed Time(ms): ", response.elapsed); Print("Headers:\n", response.headers); Print("Cookies: ", response.cookies); Print("Response: ",response.text); Print("JSON:\n", response.json["url"].ToStr()); }