在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库·综合运用
- MT5 向外部接口打请求时的原始头信息
- 用 MT5 脚本发 POST 测外部接口
- 正文
- 正文
- 用 PUT 把凭证推到外部接口
- 用 PUT 把 JSON 推到外部接口
- 在 MT5 里用 PATCH 改远端数据
- 抓出 MT5 发出的 HTTP 请求真身
- 用 DELETE 方法清掉远端测试资源
- 抓包看 DELETE 请求的头部与回显
- 把图表截图直接塞进 HTTP 请求
- 把文件塞进 multipart 请求体
- 无附件时的请求体拼装与 WebRequest 触发
- 把多文件塞进 HTTP 表单的二进制拼装
- 用扩展名反查 MIME 类型的落地片段
- 把 HTTP 回应字段全打印出来
- 从日志看 XAUUSD 日线请求的余量与响应
- 在 MT5 里直接拉取并落盘网络文件
- 用 CSession 把 cookies 带进 GET 请求
- 看一次 HTTP 回测的终端原貌
- 在 MT5 里跑通第一笔 HTTP 请求
- 给 MQL5 会话塞进 Basic 鉴权
- 抓出 MT5 发出的 HTTP 头到底长啥样
- 把参数拼进请求地址的两种写法
- 抓包看 MT5 发出的 HTTP 请求长什么样
- 抓包看 WebRequest 到底发了什么
- 一点提醒
MT5 向外部接口打请求时的原始头信息
在 MT5 里用 EA 或脚本对外发起 HTTP 请求时,终端会把完整请求头打印到日志。上面这段是在 XAUUSD 日线图上跑测试时抓到的记录,时间戳统一停在 09:50:48.904,说明请求是瞬时批量构造的。 Content-Type 标成了 application/json,Content-Length 为 379 字节,Connection 用的 keep-alive,后端返回头里 Server 是 gunicorn/19.9.0。黄金外汇品种外接 API 时,这类头字段决定网关是否放行,XAUUSD 波动大、断线重连频繁,keep-alive 能减少握手损耗。 响应头里 Access-Control-Allow-Origin 为 * 且 Access-Control-Allow-Credentials 为 true,意味着任意域可带凭证访问,实盘接外部信号源有凭证泄露可能,贵金属交易本身杠杆高、风险大,别把测试接口直接挪到 live 账户。 User-Agent 暴露了终端版本:MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)。若你的服务商按 UA 限流,升级终端后需重新核对请求策略。
◍ 用 MT5 脚本发 POST 测外部接口
在 MT5 里做 AIGC 信号中转,第一步是确认 EA/脚本能稳定打外部 HTTP 接口。上面这段日志是 XAUUSD D1 图表上跑请求库的真实输出:09:50:48.904 向 https://httpbin.org/get 发了 GET,回显里带 "origin":"197.250.227.26",说明 broker 服务器出口 IP 被对方记录,外汇/贵金属这种跨网调用存在被中间节点限速的可能。 静态方法 post() 封装得很薄,只是把 "POST" 透传给底层 request(),默认超时 5000 毫秒、is_json=true。你在 OnStart 里拼一个 {"username":"omega","password":"secret123"} 的 JSON 串,丢给 https://httpbin.org/post,就能拿回结构化的 CResponse。 日志尾部 15:32:13.093 显示 Status Code: 200,证明 POST 通路在 XAUUSD 图表可用。开 MT5 把 requests.mqh 包含进去直接跑,重点看 response.elapsed 这个毫秒数——若反复超过 1000ms,实盘触发信号时就要考虑异步或本地缓存。
class="kw">static CResponse post(class="kw">const class="type">class="kw">string url, class="kw">const class="type">class="kw">string data = "", class="kw">const class="type">class="kw">string headers = "", class="kw">const class="type">int timeout = class="num">5000, class="kw">const class="type">bool is_json=true) { class="kw">return request("POST", url, data, headers, timeout, is_json); } 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">//--- class="type">class="kw">string json = "{\"username\": \"omega\", \"password\": \"secret123\"}"; CResponse response = CSession::post("https:class=class="str">"cmt">//httpbin.org/post",json); 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()); }
「正文」
OP <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) Reason: OK KE <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) URL: https:<span class="comment">//httpbin.org/post</span> GR <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) OK: <span class="macro">true</span> LD <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) Elapsed Time (ms): <span class="number">1578</span> GL <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</spa
正文
NL <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) <span class="string">"files"</span>: {}, QJ <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) <span class="string">"form"</span>: {}, PS <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) <span class="string">"headers"</span>: { DJ <span class="number">0</span> <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span> Requests test (XAUUSD,D1) <span class="string">"Accept"</span>: <span class="string">"*/*"</span>, HO <span class="number">0</span> &nbs
◍ 用 PUT 把凭证推到外部接口
在 MT5 里做跨系统验证时,PUT 方法常被用来把账号类数据推给远端服务。上面这段日志显示,XAUUSD 的 D1 图表在 15:32:13.093 跑了一次请求测试,目标指向 httpbin.org/post,源 IP 为 197.250.227.26,说明请求是从该终端外发的。 底层封装很简单:静态方法 put() 直接调 request() 并写死动词为 "PUT",默认超时 5000 毫秒、is_json 为真,数据体为空串也允许。 脚本里构造了一个 JSON 字符串 {"username": "omega", "password": "secret123"},通过 CSession::put 发往 https://httpbin.org/put,随后把 status_code、reason、elapsed 等字段逐一 Print 出来。你在 MT5 里装好 requests.mqh,把这段直接丢进脚本跑,就能在专家日志看到回显耗时与响应体。 黄金外汇品种涉及外部网络请求时波动与断连风险偏高,这类凭证传输仅适合本地沙盒或测试域名验证,别直接挂实盘地址。
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">//--- class="type">class="kw">string json = "{\"username\": \"omega\", \"password\": \"secret123\"}"; CResponse response = CSession::put("https:class=class="str">"cmt">//httpbin.org/put",json); 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()); } class="kw">static CResponse put(class="kw">const class="type">class="kw">string url, class="kw">const class="type">class="kw">string data = "", class="kw">const class="type">class="kw">string headers = "", class="kw">const class="type">int timeout = class="num">5000, class="kw">const class="type">bool is_json=true) { class="kw">return request("PUT", url, data, headers, timeout, is_json); }
「用 PUT 把 JSON 推到外部接口」
在 MT5 里做 AIGC 信号中转,常需要把一段 JSON 推给外部服务。下面这段脚本演示了用 CSession::put 向 httpbin.org 发 PUT 请求,并取回响应原文。 实测日志里,XAUUSD 的 D1 图表上跑出的 Reason 是 OK,响应体 data 字段回显为 {"update":true},说明 15 字节的 JSON 被服务端原样接收。Content-Type 被终端自动标成 application/json,Content-Length 正是 15。 外汇与贵金属行情波动剧烈、杠杆风险高,这类外部请求只适合做信号备份或模型调用,别拿它当下单通道。
class="type">void OnStart() { class=class="str">"cmt">//--- class="type">class="kw">string json = "{\"update\": true}"; CResponse response = CSession::put("https:class=class="str">"cmt">//httpbin.org/put", json, "", class="num">5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
class="type">void OnStart() { class=class="str">"cmt">//--- class="type">class="kw">string json = "{\"update\": true}"; CResponse response = CSession::put("https:class=class="str">"cmt">//httpbin.org/put", json, "", class="num">5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
在 MT5 里用 PATCH 改远端数据
做价格行为分析时常要把本地算好的形态标记回写远端服务,MT5 的 HTTP 封装里 PATCH 方法比 PUT 更轻,只发差异字段。下面这段日志来自 XAUUSD 的 D1 图表,15:51:02.874 时刻向 httpbin.org/put 发请求,回包里 "update": true 表明服务端接受了增量更新,这种响应结构可直接拿来校验信号同步是否生效。 日志尾部另一次 16:33:45.258 的 patch 调用打在 XAUUSD,D1 上,Reason 返回 OK,Response 拿到 { 开头的 json,说明 5000 毫秒超时内完成了黄金日线图外的接口握手。外汇与贵金属品种波动剧烈,这类外网请求若卡在滑点时段可能丢信号,实盘前先在策略测试器跑通再上。 代码层先定义静态 patch 方法,把动词、url、data、headers、timeout、is_json 透传进通用 request。OnStart 里拼一个 {"patched": 1} 的 json 串,调 CSession::patch 打给 https://httpbin.org/patch,最后 Print 出 reason 和 text 方便肉眼看回执。
class="kw">static CResponse patch(class="kw">const class="type">class="kw">string url, class="kw">const class="type">class="kw">string data = "", class="kw">const class="type">class="kw">string headers = "", class="kw">const class="type">int timeout = class="num">5000, class="kw">const class="type">bool is_json=true) { class="kw">return request("PATCH", url, data, headers, timeout, is_json); } 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">//--- class="type">class="kw">string json = "{\"patched\": class="num">1}"; CResponse response = CSession::patch("https:class=class="str">"cmt">//httpbin.org/patch", json, "", class="num">5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
◍ 抓出 MT5 发出的 HTTP 请求真身
在 XAUUSD 的 D1 图表上跑一次请求测试,终端会在日志里打印出完整报文。上面这段就是在 16:33:45.258 由 MetaTrader 5 终端(版本 5.5120,Windows NT 10.0.19045 x64)发出的 POST 记录,目标 Host 为 httpbin.org。 报文头里 Content-Length 是 13,Content-Type 为 application/json,说明负载是一段很短的 JSON。实际 json 字段只有 "patched": 1,对应 data 字段里打印出的 {"patched":1},证明请求体被正确序列化并送达。 User-Agent 明确写着 MetaTrader 5 Terminal/5.5120,服务端据此能识别你是 MT5 客户端。X-Amzn-Trace-Id 为 Root=1-6867d837-47b9eb772b7ec6300016aa79,可用于在 httpbin 或网关侧排查那一次调用。 开 MT5 自带的 HTTP 请求示例,把 URL 换成 httpbin.org/post,就能在专家日志里复现上面这串输出。黄金外汇品种波动剧烈、杠杆风险高,这类外网请求仅作连通性验证,别拿实盘账户乱联未知接口。
「用 DELETE 方法清掉远端测试资源」
在 MT5 里做 REST 交互,DELETE 请求常用于通知服务端销毁临时对象(如测试会话、挂单回执)。下面这段静态封装把方法写死为 DELETE,默认超时 5000 毫秒、按 JSON 解析返回。 static CResponse delete_(const string url, const string headers = "", const int timeout = 5000, const bool is_json=true) { return request("DELETE", url, "", headers, timeout, is_json); } 调用时只需给 URL:CResponse response = CSession::delete_("https://httpbin.org/delete", "", 5000, true); 回测日志显示在 XAUUSD D1 图表上,16:43:03.046 返回 Reason: OK,证明服务端接受了删除指令。 黄金品种波动剧烈、杠杆风险高,任何对外请求都应在模拟盘先跑通;实盘调用第三方接口可能因网络或风控导致指令丢失,概率上不保证一定生效。
class="kw">static CResponse delete_(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">const class="type">bool is_json=true) { class="kw">return request("DELETE", url, "", headers, timeout, is_json); } 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::delete_("https:class=class="str">"cmt">//httpbin.org/class="kw">delete", "", class="num">5000, true); Print("Reason: ", response.reason); Print("Response: ",response.text); }
抓包看 DELETE 请求的头部与回显
在 XAUUSD 的 D1 图表上跑一轮请求测试,终端会在 16:43:03.046 同一毫秒内打出完整请求头与远端回显,方便确认 MT5 发出的 HTTP 行为到底长什么样。 从日志可见,User-Agent 明确带出「MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)」,Host 指向 httpbin.org,Content-Type 为 application/json 且 Content-Length 为 0,说明这是一次无正文体的 DELETE 探测。 回显里 origin 字段值为 197.250.227.26,url 为 https://httpbin.org/delete,json 为 null,X-Amzn-Trace-Id 形如 Root=1-6867da67-1926ebf246353ab24461aed9,可用于比对网关链路。黄金外汇品种波动剧烈、杠杆高风险大,这类网络调用仅作终端诊断,不直接关联任何交易信号。 开 MT5 把这段日志对照自己 EA 的 WebRequest 参数,就能确认本地出口 IP 与请求头是否被经纪商环境篡改。
LG class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "Accept-Encoding": "gzip, deflate", KO class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "Accept-Language": "en;q=class="num">0.5", QQ class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "Content-Length": "class="num">0", HE class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "Content-Type": "application/json", LS class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "Host": "httpbin.org", GF class="num">0 class="num">16:class="num">43:class="num">03.046 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)", JK class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "X-Amzn-Trace-Id": "Root=class="num">1-6867da67-1926ebf246353ab24461aed9" LK class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) }, FL class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "json": null, PG class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "origin": "class="num">197.250.class="num">227.26", PO class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) "url": "https:class=class="str">"cmt">//httpbin.org/class="kw">delete" HE class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1) } QL class="num">0 class="num">16:class="num">43:class="num">03.046 Requests test(XAUUSD,D1)
◍ 把图表截图直接塞进 HTTP 请求
MQL5 内置的 WebRequest 只能发普通文本请求,要带文件得自己拼 multipart/form-data。CSession 类里最麻烦的就是这部分:从本地读二进制、按扩展名判断类型、再写对 HTTP 标头,否则服务端收不到东西。 当 files 数组为空,函数就退化成一次普通 POST;只要数组里有路径,就自动把 Content-Type 改成 multipart/form-data,用 boundary 隔开字段和文件。final_body 这个 CArrayChar 在循环里把 JSON 和文件流粘成一个 char 数组,等价于网页里提交的 form。 实际验证路径:截图存到 MQL5 数据目录(Terminal 里 File→Open Data Folder 可定位),把 tempfiles.org 加进 MT5 工具→选项的允许 WebRequest 列表。POST 成功会返回 JSON,里面的 url 字段就是托管链接,浏览器打开就能看图。外汇与贵金属图表信号通过公网转发存在泄露持仓布局的风险,仅建议用临时匿名服务。 下面这段是 request 方法的前半段,关键在 boundary 常量和 use_multipart 判断:文件数大于 0 才走多部件分支,否则照常发文本。
CResponse CSession::request(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> method, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> url, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> data, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> &files[], <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> headers = <span class="class="type">class="kw">string">""</span>, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> timeout = <span class="number">class="num">5000</span>, <span class="keyword">class="kw">const</span> <span class="keyword">class="type">bool</span> is_json=<span class="macro">true</span>) { <span class="keyword">class="type">char</span> result[]; <span class="keyword">class="type">class="kw">string</span> result_headers; <span class="keyword">class="type">class="kw">string</span> temp_headers = m_headers; <span style="background-class="type">class="kw">color:rgb(class="num">232, class="num">183, class="num">215);"> <span class="keyword">class="type">class="kw">string</span> boundary = <span class="class="type">class="kw">string">"----WebKitFormBoundary7MA4YWxkTrZu0gW"</span>; <span class="comment">class=class="str">"cmt">//for setting boundaries between data types and files in the form data</span> </span> CArrayChar final_body; <span class="comment">class=class="str">"cmt">//Final body class="type">uchar array</span> CResponse response; <span class="comment">class=class="str">"cmt">//a structure containing various response fields</span> <span class="comment">class=class="str">"cmt">// Append user headers</span> <span class="keyword">if</span> (headers != <span class="class="type">class="kw">string">""</span>) temp_headers += headers; <span class="keyword">class="type">bool</span> use_multipart = <span class="functions">ArraySize</span>(files) > <span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">//Check if files are attached </span> <span class="comment">class=class="str">"cmt">//--- Create a multi part request</span> <span class="keyword">if</span> (use_multipart) <span class="comment">class=class="str">"cmt">// If multipart, assemble full body(JSON + files)</span> { temp_headers = UpdateHeader(temp_headers, <span class="class="type">class="kw">string">"Content-Type"</span>, <span class="class="type">class="kw">string">"multipart/form-data; boundary="</span> + boundary + <span class="class="type">class="kw">string">"\r\n"</span>); <span class="comment">class=class="str">"cmt">//Update the headers</span> <span class="comment">class=class="str">"cmt">//--- JSON part(or form data)</span> <span class="keyword">if</span> (<span class="functions">StringLen</span>(data) > <span class="number">class="num">0</span>) { <span class="keyword">class="type">class="kw">string</span> json_data = <span class="class="type">class="kw">string">""</span>; <span class="keyword">if</span> (is_json) <span class="comment">class=class="str">"cmt">//if Json data is given alongside the files </span> { CJAVal js(<span class="macro">NULL</span>, jtUNDEF); <span class="keyword">if</span> (js.Deserialize(data, <span class="macro">CP_UTF8</span>)) js.Serialize(json_data); <span class="comment">class=class="str">"cmt">//Serialize the JSON data</span> } <span class="keyword">class="type">class="kw">string</span> json_part = <span class="class="type">class="kw">string">"--"</span> + boundary + <span class="class="type">class="kw">string">"\r\n"</span>; json_part += <span class="class="type">class="kw">string">"Content-Disposition: form-data; name=\"metadata\"\r\n"</span>; json_part += <span class="class="type">class="kw">string">"Content-Type: application/json\r\n\r\n"</span>; json_part += json_data + <span class="class="type">class="kw">string">"\r\n"</span>;
「把文件塞进 multipart 请求体」
往 HTTP multipart/form-data 请求里挂文件,核心是先以二进制读入再拼头部。下面这段 MT5 代码遍历文件数组,逐个用 FILE_BIN|FILE_SHARE_READ 打开,失败就跳过并打日志,不中断整个发送流程。 文件读入靠 FileReadArray 一次性搬进 char 数组,ArrayResize 按 FileSize 返回值精确分配内存;读不到数据同样跳过。实测若 fsize 为 0,FileReadArray 返回 0,会触发 continue 而非崩溃。 每个文件前要手拼边界头和 Content-Disposition,Content-Type 由 GuessContentType(filename) 推断。头部字符串必须走 StringToCharArray 带 CP_UTF8 编码转成字节,否则服务端解析中文文件名可能乱码。 文件二进制数据紧跟头部之后,末尾补 '\r''\n' 是 HTTP 表单解析的硬要求——缺了这两个字符,服务端可能把文件当成头的一部分。最后还有收尾边界,这里代码截到注释 //--- Final boundary 前。
class="type">char json_bytes[]; StringToCharArray(json_part, json_bytes, class="num">0, StringLen(json_part), CP_UTF8); final_body.AddArray(json_bytes); } class=class="str">"cmt">//--- File parts for (class="type">uint i = class="num">0; i < files.Size(); i++) { class="type">class="kw">string filename = GetFileName(files[i]); class="type">char file_data[]; class=class="str">"cmt">//for storing the file data in binary format class="type">int file_handle = FileOpen(filename, FILE_BIN | FILE_SHARE_READ); class=class="str">"cmt">// Read the file in binary format if (file_handle == INVALID_HANDLE) { printf("func=%s line=%d, Failed to read the file &class="macro">#x27;%s&class="macro">#x27;. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); class="kw">continue; class=class="str">"cmt">//skip to the next file if the current file is invalid } class="type">int fsize = (class="type">int)FileSize(file_handle); ArrayResize(file_data, fsize); if (FileReadArray(file_handle, file_data, class="num">0, fsize)==class="num">0) { printf("func=%s line=%d, No data found in the file &class="macro">#x27;%s&class="macro">#x27;. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); FileClose(file_handle); class="kw">continue; class=class="str">"cmt">//skip to the next file if the current file is invalid } FileClose(file_handle); class=class="str">"cmt">//close the current file class=class="str">"cmt">//--- Append files header and content type as detected to the request class="type">class="kw">string file_part = "--" + boundary + "\r\n"; file_part += StringFormat("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", filename); file_part += StringFormat("Content-Type: %s\r\n\r\n", GuessContentType(filename)); class="type">char file_header[]; StringToCharArray(file_part, file_header, class="num">0, StringLen(file_part), CP_UTF8); class=class="str">"cmt">//UTF-class="num">8 Encoding is a must final_body.AddArray(file_header); class=class="str">"cmt">//Add the file header final_body.AddArray(file_data); class=class="str">"cmt">//Add the file in binary format, the actual file class=class="str">"cmt">//--- append the new line — critical for HTTP form parsing. final_body.Add(&class="macro">#x27;\r&class="macro">#x27;); final_body.Add(&class="macro">#x27;\n&class="macro">#x27;); } class=class="str">"cmt">//--- Final boundary
无附件时的请求体拼装与 WebRequest 触发
当不附带文件时,代码走 else 分支直接处理 JSON 或纯表单数据。若 is_json 为真,先用 CJAVal 以 CP_UTF8 反序列化校验,再序列化回 body_data,并把 Content-Type 强制设为 application/json;否则沿用外部传入的 headers。 关键一步在 StringToCharArray 调用:显式指定 CP_UTF8 编码,等价于 Python requests 里的 utf-8 正文。很多 MT5 调用方返回乱码,根因就是漏了这第三个参数,默认 ANSI 会截断非英文字符。 final_body 拼完后用 CArray2Array 摊平为 char 数组。调试模式下 MQL_DEBUG 为真时会 Print 出最终正文,便于在 MT5 终端直接核对 boundary 与字段。 真正发请求靠 WebRequest:记录 GetTickCount 起点,超时与 headers 一并传入。status 为 -1 即系统级失败,用 ErrorDescription(GetLastError()) 打印具体原因,并令 response.status_code=0 提前返回。 成功时 response.elapsed 为毫秒级耗时,ok 字段定义为 status 落在 [200,400) 区间。末尾再尝试把 response.text 丢给 CJAVal 反序列化,能解就挂到 response.json,调用方据此判断接口是否回了结构化数据。
class="type">class="kw">string closing = "--" + boundary + "--\r\n"; class="type">char closing_part[]; StringToCharArray(closing, closing_part); final_body.AddArray(closing_part); } else class=class="str">"cmt">// no files attached { class=class="str">"cmt">//--- If it&class="macro">#x27;s just JSON or plain form data class="type">class="kw">string body_data = data; if (is_json) { CJAVal js(NULL, jtUNDEF); if (js.Deserialize(data, CP_UTF8)) js.Serialize(body_data); temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json"); } else temp_headers = UpdateHeader(temp_headers, headers); class=class="str">"cmt">//--- class="type">char array[]; StringToCharArray(body_data, array, class="num">0, StringLen(body_data), CP_UTF8); class=class="str">"cmt">//Use UTF-class="num">8 similar requests in Python, This is very crucial final_body.AddArray(array); } class="type">char final_body_char_arr[]; CArray2Array(final_body, final_body_char_arr); if (MQLInfoInteger(MQL_DEBUG)) Print("Final body:\n",CharArrayToString(final_body_char_arr, class="num">0 , final_body.Total(), CP_UTF8)); class=class="str">"cmt">//--- Send the request class="type">uint start = GetTickCount(); class=class="str">"cmt">//starting time of the request class="type">int status = WebRequest(method, url, temp_headers, timeout, final_body_char_arr, result, result_headers); class=class="str">"cmt">//trigger a webrequest function if(status == -class="num">1) { PrintFormat("WebRequest failed with error %s", ErrorDescription(GetLastError())); response.status_code = class="num">0; class="kw">return response; } class=class="str">"cmt">//--- Fill the response class="kw">struct response.elapsed = GetTickCount() - start; response.text = CharArrayToString(result); response.status_code = status; response.headers = result_headers; response.url = url; response.ok = (status >= class="num">200 && status < class="num">400); response.reason = WebStatusText(status); ArrayCopy(response.content, result); class=class="str">"cmt">//--- CJAVal js; if (js.Deserialize(response.text)) response.json = js; class="kw">return response; } temp_headers = UpdateHeader(temp_headers, "Content-Type", "multipart/form-data; boundary=" + boundary + "\r\n"); class=class="str">"cmt">//Update the headers class=class="str">"cmt">//--- File parts for (class="type">uint i = class="num">0; i < files.Size(); i++) { class="type">class="kw">string filename = GetFileName(files[i]); class="type">char file_data[]; class=class="str">"cmt">//for storing the file data in binary format class="type">int file_handle = FileOpen(filename, FILE_BIN | FILE_SHARE_READ); class=class="str">"cmt">// Read the file in binary format
◍ 把多文件塞进 HTTP 表单的二进制拼装
向 AIGC 接口批量推历史 K 线或截图时,MT5 端常需手动拼 multipart/form-data。这段逻辑先判文件句柄是否 INVALID_HANDLE,失败就打印 func、line、文件名与错误描述并 continue,跳过坏文件继续循环。 读文件用 FileSize 取字节数,ArrayResize 把 file_data 拉到对应长度,再 FileReadArray 一次性灌入;返回 0 表示没读到数据,关句柄后跳过。注意 FileClose 必须在每次读完执行,否则句柄泄漏会让后续请求在的第 30~50 个文件附近开始失败。 拼 header 时用 StringFormat 写 Content-Disposition 与 Content-Type,StringToCharArray 转 UTF-8 字节数组后 AddArray 进 final_body,紧接二进制正文,最后补 '\r' '\n'——缺这两个字符,服务端解析边界会直接断。 GuessContentType 靠后缀小写匹配:.txt→text/plain,.json→application/json,.png→image/png。自己加格式时照这个 if 链补,别漏 StringToLower 归一化,否则 .PNG 会落空返回默认型。
if (file_handle == INVALID_HANDLE) { printf("func=%s line=%d, Failed to read the file &class="macro">#x27;%s&class="macro">#x27;. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); class="kw">continue; class=class="str">"cmt">//skip to the next file if the current file is invalid } class="type">int fsize = (class="type">int)FileSize(file_handle); ArrayResize(file_data, fsize); if (FileReadArray(file_handle, file_data, class="num">0, fsize)==class="num">0) { printf("func=%s line=%d, No data found in the file &class="macro">#x27;%s&class="macro">#x27;. Error = %s",__FUNCTION__,__LINE__,filename,ErrorDescription(GetLastError())); FileClose(file_handle); class="kw">continue; class=class="str">"cmt">//skip to the next file if the current file is invalid } FileClose(file_handle); class=class="str">"cmt">//close the current file class=class="str">"cmt">//--- Append files header and content type as detected to the request class="type">class="kw">string file_part = "--" + boundary + "\r\n"; file_part += StringFormat("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n", filename); file_part += StringFormat("Content-Type: %s\r\n\r\n", GuessContentType(filename)); class="type">char file_header[]; StringToCharArray(file_part, file_header, class="num">0, StringLen(file_part), CP_UTF8); class=class="str">"cmt">//UTF-class="num">8 Encoding is a must final_body.AddArray(file_header); class=class="str">"cmt">//Add the file header final_body.AddArray(file_data); class=class="str">"cmt">//Add the file in binary format, the actual file class=class="str">"cmt">//--- append the new line — critical for HTTP form parsing. final_body.Add(&class="macro">#x27;\r&class="macro">#x27;); final_body.Add(&class="macro">#x27;\n&class="macro">#x27;); } class="type">class="kw">string CSession::GuessContentType(class="type">class="kw">string filename) { StringToLower(filename); class=class="str">"cmt">// Normalize for case-insensitivity if(StringFind(filename, ".txt") >= class="num">0) class="kw">return "text/plain"; if(StringFind(filename, ".json") >= class="num">0) class="kw">return "application/json"; if(StringFind(filename, ".xml") >= class="num">0) class="kw">return "application/xml"; class=class="str">"cmt">//... other files class=class="str">"cmt">//--- Images if(StringFind(filename, ".png") >= class="num">0) class="kw">return "image/png";
「用扩展名反查 MIME 类型的落地片段」
在 MT5 里做图表自动上传时,服务端往往要求带正确的 Content-Type。上面这段散落的判断逻辑,就是按文件名后缀硬查 MIME 类型:命中 .jpg/.jpeg 回 image/jpeg,命中 .mp3 回 audio/mpeg,未命中任何已知后缀则统一回 application/octet-stream 作兜底。 StringFind(filename, ".jpg") >= 0 的意思是「文件名里出现了该子串」,它不校验真实文件头,所以把 xxx.jpg.txt 也会误判成图片——实盘脚本若依赖此逻辑,得先确认扩展名可信。 下面这段 OnStart 里只塞了一个 "chart.jpg" 进 files 数组,然后走 CSession::request 发 POST 到 tmpfiles.org 的上传接口。你可以直接把这段代码粘进脚本,把图表截图命名成 chart.jpg 丢进终端目录,跑一遍看返回的临时链接,验证 MIME 推断是否被正确携带。 外汇与贵金属行情波动剧烈,自动化上传图表若用于信号分享或复盘,注意接口稳定性与隐私泄露风险,任何外部 API 都有失效可能。
if(StringFind(filename, ".jpg") >= class="num">0 || StringFind(filename, ".jpeg") >= class="num">0) class="kw">return "image/jpeg"; if(StringFind(filename, ".gif") >= class="num">0) class="kw">return "image/gif"; class=class="str">"cmt">//...etc class=class="str">"cmt">//--- Audio if(StringFind(filename, ".mp3") >= class="num">0) class="kw">return "audio/mpeg"; if(StringFind(filename, ".wav") >= class="num">0) class="kw">return "audio/wav"; if(StringFind(filename, ".ogg") >= class="num">0) class="kw">return "audio/ogg"; class=class="str">"cmt">//--- Video if(StringFind(filename, ".mp4") >= class="num">0) class="kw">return "video/mp4"; if(StringFind(filename, ".avi") >= class="num">0) class="kw">return "video/x-msvideo"; if(StringFind(filename, ".mov") >= class="num">0) class="kw">return "video/quicktime"; if(StringFind(filename, ".webm") >= class="num">0) class="kw">return "video/webm"; if(StringFind(filename, ".mkv") >= class="num">0) class="kw">return "video/x-matroska"; class=class="str">"cmt">//--- Applications if(StringFind(filename, ".pdf") >= class="num">0) class="kw">return "application/pdf"; if(StringFind(filename, ".zip") >= class="num">0) class="kw">return "application/zip"; class=class="str">"cmt">//... etc class=class="str">"cmt">//--- Microsoft Office if(StringFind(filename, ".doc") >= class="num">0) class="kw">return "application/msword"; if(StringFind(filename, ".docx") >= class="num">0) class="kw">return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; if(StringFind(filename, ".xls") >= class="num">0) class="kw">return "application/vnd.ms-excel"; class=class="str">"cmt">//...etc class="kw">return "application/octet-stream"; class=class="str">"cmt">// Default fallback } 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">//--- class="type">class="kw">string files[] = {"chart.jpg"}; CResponse response = CSession::request("POST","https:class=class="str">"cmt">//tmpfiles.org/api/v1/upload","",files);
把 HTTP 回应字段全打印出来
在 MT5 里用 WebRequest 封装的对象,回传后第一件事应是把响应结构逐字段打印,而不是只看状态码。下面这段代码把一次上传接口的完整回应都丢进日志,方便你确认服务端到底回了什么。
打印结果显示:对 https://tmpfiles.org/api/v1/upload 的调用返回 Status Code 200、OK 为 true,说明请求层本身通畅;但 Elapsed Time 达到 1594 毫秒,在 XAUUSD D1 图表上跑一次就吃掉约 1.6 秒,实盘里若放在 OnTick 高频调用会明显拖慢执行。
响应头里 X-RateLimit-Limit 是 60,意味着同一时段最多 60 次调用,超出可能触发限流。做贵金属信号推送类 EA 时,这个上限要写进重试逻辑,否则外汇/贵金属这种高波动品种上容易因请求被掐而漏单。
顺手看一眼 Headers 里的 Server: nginx/1.22.1 和 Content-Type: application/json,能确认后端没挂 CDN 伪装,返回体可直接用 response.json 解析。
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());
◍ 从日志看 XAUUSD 日线请求的余量与响应
在 MT5 Experts 日志里跑 Requests test 对 XAUUSD 的 D1 周期做外链请求时,同一毫秒(17:06:25.063)会打出多行前缀标记。GN 行显示 X-RateLimit-Remaining: 59,说明该接口当前配额还剩 59 次,临近阈值时需降低轮询频率。 RM 行给出了实际响应体:{"status":"success","data":{"url":"http://tmpfiles.org/5459540/chart.png"}},代表图表快照已生成并可经临时链接取回。CN 行的 Access-Control-Allow-Origin: * 则表示任意域可跨域拉取该 PNG,便于在小布看板里直接嵌图。 NF、ED、LS、HJ 等行多为空载荷(Cookies、JSON 等未带内容),验证时若只关心图表 URL,可只过滤 RM 与 GN 两行。黄金外汇接口调用波动大、高风险,配额耗尽会直接断图,建议本地缓存上一张成功响应。
GN class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1) X-RateLimit-Remaining: class="num">59 CN class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1) Access-Control-Allow-Origin: * NF class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1) ED class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1) Cookies: RM class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1) Response: {"status":"success","data":{"url":"http:class=class="str">"cmt">//tmpfiles.org/class="num">5459540/chart.png"}} LS class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1) JSON: HJ class="num">0 class="num">17:class="num">06:class="num">25.063 Requests test(XAUUSD,D1)
「在 MT5 里直接拉取并落盘网络文件」
做价格行为分析时,经常要把外部 CSV、训练好的模型权重或二进制素材喂给 EA。MQL5 借助 requests 库思路,可以用 HTTP GET 把远端文件拉进终端数据目录,省去手动拷贝。 只要 API 端点正确,封装好的 get 方法会返回二进制内容,挂在 CResponse::content 里。比如向 httpbin.org 要一张 jpeg,成功执行后,MQL5/Files 下就会出现名为 image.jpg 的图片文件。 CSession 类所有方法都是静态的,等于一个“静态类”,目的是让开发者像 Python 的 requests 模块那样按需取用。要么构造实例并传入 header 与 cookie,复用同一会话;要么不实例化,只调静态函数发一次性请求,每次自己管头与 cookie。 下面这段把取图与写文件串起来:先 get 拿到响应,再用 FileOpen 以二进制写模式开 image.jpg,FileWriteArray 把 content 整段写进去,最后关句柄。若 handle 为 INVALID_HANDLE 或写回 0 字节,就打印错误码,方便你当场在 MT5 Experts 日志里排错。
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">//--- Get the image file from the web CResponse response = requests.get("https:class=class="str">"cmt">//httpbin.org/image/jpeg"); } class="type">void OnStart() { class=class="str">"cmt">//--- Get the image file from the web CResponse response = requests.get("https:class=class="str">"cmt">//httpbin.org/image/jpeg"); class=class="str">"cmt">//--- Saving an image received in binary format stored in response.content class="type">int handle = FileOpen("image.jpg", FILE_WRITE|FILE_BIN|FILE_SHARE_WRITE); class=class="str">"cmt">//Open a .jpg file for writting an image to it if (handle == INVALID_HANDLE) class=class="str">"cmt">//Check the handle { printf("Failed to open an Image. Error=%s",ErrorDescription(GetLastError())); class="kw">return; } if (FileWriteArray(handle, response.content)==class="num">0) class=class="str">"cmt">//write all binary data to a image.jpg file { printf("Failed to write an Image. Error=%s",ErrorDescription(GetLastError())); class="kw">return; } FileClose(handle); } class CSession { class="kw">protected: class=class="str">"cmt">//.... other lines of code class="kw">public: CSession(class="kw">const class="type">class="kw">string headers, class="kw">const class="type">class="kw">string cookies=""); class=class="str">"cmt">// Provides headers cookies persistance ~CSession(class="type">void); class="kw">static class="type">void SetCookie(class="kw">const class="type">class="kw">string cookie) { if (StringLen(m_cookies) > class="num">0) m_cookies += "; "; m_cookies += cookie; } class="kw">static class="type">void ClearCookies() {
用 CSession 把 cookies 带进 GET 请求
在 MT5 里做 HTTP 交互时,很多行情或账户接口依赖会话态。上面这段把 CSession 构造时传入的 cookies 字符串(例如 sessionid=abc123)直接带进 get 请求,目标地址用了 https://httpbin.org/cookies 做回显验证。 代码里 get 是高层封装,内部调 request("GET", url, "", files, headers, timeout, false),注意最后一个参数 is_json 传了 false,说明这次只取文本不带 JSON 解析负担。 实跑日志里有一行关键数据:11:38:36.272 在 XAUUSD,D1 图表上跑脚本,返回 Status Code: 200。这意味着会话与 cookie 透传链路是通的,你可以照抄这个 CSession 用法去对接自己的外汇/贵金属数据源。 外汇与贵金属品种波动剧烈、杠杆风险高,任何自动化取数仅作分析辅助,不代表行情方向判断。
m_cookies = ""; } class="kw">static class="type">void SetBasicAuth(class="kw">const class="type">class="kw">string username, class="kw">const class="type">class="kw">string password); class=class="str">"cmt">//--- class="kw">static CResponse request(class="kw">const class="type">class="kw">string method, class="kw">const class="type">class="kw">string url, class="kw">const class="type">class="kw">string data, class="kw">const class="type">class="kw">string &files[], class="kw">const class="type">class="kw">string headers = "", class="kw">const class="type">int timeout = class="num">5000, class="kw">const class="type">bool is_json=true); class=class="str">"cmt">// High-level request helpers 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="type">class="kw">string files[]; class="kw">return request("GET", url, "", files, headers, timeout, false); } class=class="str">"cmt">//... other functions } 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">//--- class="type">class="kw">string headers = "Content-Type: application/json;"; class="type">class="kw">string cookies = "sessionid=abc123"; CSession session(headers, cookies); class=class="str">"cmt">//New session, class constructor calle with CResponse response = session.get("https:class=class="str">"cmt">//httpbin.org/cookies"); //receive cookies from the server Print("HTTP response"); 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 text: ",response.text); Print("--> JSON:\n", response.json.ToStr()); }
◍ 看一次 HTTP 回测的终端原貌
在 MT5 专家日志里跑一次 XAUUSD 的 D1 周期请求测试,能直接看到网络层往返的全貌。上面这段日志是真实打印:11:38:36.272 同一毫秒内,KR/NG/IF/QQ 等标记依次吐出,说明请求同步发出并回收,没有分帧延迟。 其中 QQ 行给出 Elapsed Time (ms): 2141,也就是这次对外请求往返耗时约 2.14 秒。黄金品种的高波动时段若依赖外部接口做辅助判断,这个延迟可能让信号迟到,需自行评估风险。 响应头里 Content-Length: 49、Server: gunicorn/19.9.0、Access-Control-Allow-Origin: * 都原样返回,证明 MT5 的 WebRequest 能完整拿到跨域 JSON。下方 Cookies 段显示 sessionid=abc123,说明目标站的下发逻辑被如实解析,可用于后续带态请求。 打开 MT5 按 F4 编译一段 WebRequest demo,把 URL 换成 httpbin.org/cookies,就能复现这套日志。外汇与贵金属交易本身高风险,外部请求仅作工具验证,不代表任何方向建议。
「在 MT5 里跑通第一笔 HTTP 请求」
想在 MT5 里直接拉外部数据,第一步是引入 requests.mqh 这个网络会话封装。上面这段脚本在 XAUUSD 的 D1 图表上做了一件最小验证:向 httpbin.org/get 发一个 GET 请求,把回包状态、耗时和正文打印到日志。 日志里 RN/DE/EM/MD/GH 那几行是 11:38:36.272 同一毫秒打出的 Requests test (XAUUSD,D1) 标记,说明脚本加载和会话初始化在同一 tick 内完成,没有额外阻塞。 代码核心只有一行:CResponse response = CSession::get("https://httpbin.org/get"); 其余全是 Print 把 response 的各个字段吐出来。你可以直接把这段贴进脚本文件,在 MT5 按 F5 编译运行,观察状态行里 status_code 是否为 200、elapsed 字段显示多少毫秒——本地到 httpbin 通常几十毫秒内返回。 外汇与贵金属行情接口若走外部 HTTP,需注意网络抖动和券商环境限制,实盘使用前先在模拟盘验证连通性与失败重试逻辑。
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"); //The get request Print("HTTP response"); 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 text: ",response.text); Print("--> JSON:\n", response.json.ToStr()); }
给 MQL5 会话塞进 Basic 鉴权
Python 的 requests 允许在 get 调用里直接带 auth 元组,而自写的 MQL5 会话类把这件事拆了出来:单独用 SetBasicAuth 写一次,后续该实例的所有请求都自动带头。这种设计在 EA 里更顺手,免得每个接口调用都重复传密码。 SetBasicAuth 先把 username:password 拼成明文串,再做 Base64 编码,最后塞进 Authorization 头写成 'Basic <encoded>'。下面这段是类方法与调用示例的原文,逐行看逻辑: import requests response = requests.get("https://httpbin.org/headers", auth=("user", "pass")) print(response.text) void CSession::SetBasicAuth(const string username, const string password) { string credentials = username + ":" + password; string encoded = Base64Encode(credentials); //Encode the credentials m_headers = UpdateHeader(m_headers, "Authorization", "Basic " + encoded); //Update HTTP headers with the authentication information } #include <requests.mqh> void OnStart() { CSession::SetBasicAuth("user", "pass"); CResponse response = CSession::get("https://httpbin.org/headers"); Print("HTTP response"); Print("--> Headers:\n", response.headers); Print("--> Response text: ",response.text); } 前 3 行是 Python 对照:get 里 auth= 直接传元组,requests 自己处理编码。MQL5 侧从 SetBasicAuth 函数起:参数用 const string 锁死不可改;credentials 拼出 'user:pass';Base64Encode 做编码;UpdateHeader 把 'Basic '+encoded 写进 m_headers。OnStart 里先 SetBasicAuth 一次,再 get 同地址,Print 把服务端回显的头打出来。 实测日志里 XAUUSD D1 品种下跑通,服务端返回 Content-Length: 385 的 JSON,头里带 Access-Control-Allow-Credentials: true,说明鉴权头已生效。外汇贵金属接口调用涉及账户密钥,实盘接第三方 API 有凭证泄露与高风险,建议先用 httpbin 这类公开回显站验证。
class="kw">import requests response = requests.get("https:class=class="str">"cmt">//httpbin.org/headers", auth=("user", "pass")) print(response.text) class="type">void CSession::SetBasicAuth(class="kw">const class="type">class="kw">string username, class="kw">const class="type">class="kw">string password) { class="type">class="kw">string credentials = username + ":" + password; class="type">class="kw">string encoded = Base64Encode(credentials); class=class="str">"cmt">//Encode the credentials m_headers = UpdateHeader(m_headers, "Authorization", "Basic " + encoded); class=class="str">"cmt">//Update HTTP headers with the authentication information } 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">//--- CSession::SetBasicAuth("user", "pass"); class=class="str">"cmt">//Sets authentication parameters to the HTTP header CResponse response = CSession::get("https:class=class="str">"cmt">//httpbin.org/headers"); //Get the headers from the server Print("HTTP response"); Print("--> Headers:\n", response.headers); Print("--> Response text: ",response.text); }
◍ 抓出 MT5 发出的 HTTP 头到底长啥样
在 XAUUSD 的 D1 图表上跑一次请求测试,终端会在 14:23:23.885 同一毫秒内打印出完整的响应头。这套日志直接暴露了 MetaTrader 5 底层网络栈的默认行为,不用抓包也能看清。 响应里的 headers 字段显示,终端默认带 Accept:*/*、Accept-Encoding:gzip, deflate,语言偏好给的是 en;q=0.5。User-Agent 固定为 MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64),说明对方服务器能精确识别你来自哪台 Windows 机器。 Authorization 字段出现 Basic dXNlcjpwYXNz,这是 user:pass 的 Base64 明文,任何中间节点都能解码。Cookie 里写了 session=abc123;max-age=60;,意味着会话只活 60 秒,做高频外部鉴权时要自己续期。 外汇与贵金属接口调用涉及账户与密钥传输,属于高风险操作;上述头信息若落到公网嗅探者手里,可能倾向被用于重放攻击,建议只连 HTTPS 且自管 token 生命周期。
HF class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) KG class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) --> Response text: { KP class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "headers": { GH class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "Accept": "*/*", CM class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "Accept-Encoding": "gzip, deflate", DQ class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "Accept-Language": "en;q=class="num">0.5", NE class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "Authorization": "Basic dXNlcjpwYXNz", NS class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "Cookie": "session=abc123;max-age=class="num">60;", KL class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "Host": "httpbin.org", HK class="num">0 class="num">14:class="num">23:class="num">23.885 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)", NI class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) "X-Amzn-Trace-Id": "Root=class="num">1-687245ab-2065056c28f0024b71a6446f" GH class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) } IF class="num">0 class="num">14:class="num">23:class="num">23.885 Requests test(XAUUSD,D1) }
「把参数拼进请求地址的两种写法」
Python 的 requests 模块会在真正发请求前自动接管 URL 和参数,你只管把 params 字典丢进去,它拼好再发。MQL5 这边思路不同:不在每个函数里塞参数,而是单独弄一个工具函数,用原始地址加键值对生成最终 URL,函数签名更干净。
下面这段对照很直接。Python 侧 requests.get("https://httpbin.org/get", params={"param1": "value1"}) 打印出的地址是 https://httpbin.org/get?param1=value1;MQL5 侧用 CSession::BuildUrlWithParams 把 user/id/lang 三组键拼到同一根地址后,日志里落成的串是 https://httpbin.org/get?user=omega&id=123&lang=mql5+test(实测于 2025.07.12 15:41:57 XAUUSD D1 窗口)。空格被编码成加号,这是 HTTP 表单式 URL 编码的常态。
拼完就能直接喂给 CSession::get 发请求,服务端回的 JSON 里带 args 键,证明参数确实到了对面。外汇和贵金属行情接口若走这套拼参逻辑,注意 URL 里含账户类字段时走 HTTPS 才稳妥,这类品种杠杆高、断连重拼可能错过报价窗口。
class="kw">import requests response = requests.get("https:class=class="str">"cmt">//httpbin.org/get", params={"param1": "value1"}) print(response.url) https:class=class="str">"cmt">//httpbin.org/get?param1=value1 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">//--- class="type">class="kw">string keys[] = {"user", "id", "lang"}; class="type">class="kw">string values[] = {"omega", "class="num">123", "mql5 test"}; class="type">class="kw">string parent_url = "https:class=class="str">"cmt">//httpbin.org/get"; class="type">class="kw">string final_url = CSession::BuildUrlWithParams(parent_url, keys, values); class=class="str">"cmt">//builds a URL with the given parameters Print("final url: ",final_url); } class="num">2025.07.class="num">12 class="num">15:class="num">41:class="num">57.924 Requests test(XAUUSD,D1) final url: https:class=class="str">"cmt">//httpbin.org/get?user=omega&id=class="num">123&lang=mql5+test CResponse response = CSession::get(final_url); class=class="str">"cmt">//Get the headers from the server Print("HTTP response"); Print("--> Headers:\n", response.headers); Print("--> Response text: ",response.text); JK class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) --> Headers: QP class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Date: Sat, class="num">12 Jul class="num">2025 class="num">12:class="num">41:class="num">59 GMT QK class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Content-Type: application/json PM class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Content-Length: class="num">525 ED class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Connection: keep-alive FP class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Server: gunicorn/class="num">19.9.class="num">0 MH class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Access-Control-Allow-Origin: * EK class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) Access-Control-Allow-Credentials: true
抓包看 MT5 发出的 HTTP 请求长什么样
在 XAUUSD 的 D1 图表上跑一次请求测试,终端会在日志里吐出完整的回显报文。上面这段是 15:42:00.398 那一刻,MetaTrader 5 实际向外发出的参数与头域快照。 回显的 args 块里带了三个自定义字段:id 为 "123"、lang 为 "mql5 test"、user 为 "omega"。这说明你在 WebRequest 里塞的 JSON 主体,服务端能原样收到,调试接口时可以直接对照。 headers 里最值得盯的是 User-Agent:MetaTrader 5 Terminal/5.5120 (Windows NT 10.0.19045; x64)。部分 API 会按 UA 限流或拒绝非浏览器客户端,遇到 403 先查这一行。Cookie 里 session=abc123 的 max-age 只有 60 秒,黄金交易接口若依赖短时效会话,得在代码里做自动续期。 X-Amzn-Trace-Id 暴露请求走了 AWS 链路,跨境黄金报价 API 的延迟波动可能与此有关。外汇与贵金属杠杆高、滑点大,这类外部依赖越多,实盘断线风险越要提前压测。
◍ 抓包看 WebRequest 到底发了什么
在 MT5 里用 WebRequest 往外发请求,光看返回 JSON 不够,得确认实际出站的目标和参数。上面这段日志是 XAUUSD 日线图上一次测试请求的终端打印,时间截在 15:42:00.398,来源 IP 显示为 197.250.227.235。 真正发出去的 URL 是 https://httpbin.org/get?user=omega&id=123&lang=mql5+test,三个查询参数 user、id、lang 都被原样拼进 GET 串。外汇与贵金属品种上做这类外部调用,网络延迟和券商防火墙都可能让请求失败,属于高风险操作,实盘前务必在策略测试器里先跑通。 把这段日志贴进终端专家日志窗口,对照你 EA 里的 WebRequest 调用,就能验证参数有没有被编码错。若 lang 字段出现空格被转成加号而非 %20,说明 MQL5 的字符串拼接走了表单式编码,不是 bug,但服务端解析时得兼容。
CQ class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) "origin": "class="num">197.250.class="num">227.235", MD class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) "url": "https:class=class="str">"cmt">//httpbin.org/get?user=omega&id=class="num">123&lang=mql5+test" IK class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1) } LN class="num">0 class="num">15:class="num">42:class="num">00.398 Requests test(XAUUSD,D1)
「一点提醒」
把 Python 里 requests 的写法搬进 MQL5,本质只是让 MT5 能直接向外部服务器发 HTTP 请求,语法接近但模块还远没到生产级。作者把代码挂在 forge.mql5.io/omegajoctan/Requests,附带四个文件:errordescription.mqh 管错误码说明、Jason.mqh 做类 JSON 串序列化、requests.mqh 是主模块、Requests test.mq5 跑全量函数自测。 外汇与贵金属杠杆高,这类未严格测试的网络模块若直接接实盘信号,可能因超时或响应异常放大滑点风险,倾向只用于回测管道或离线研究。 真要验证,下载 ZIP 解到 Include 与 Scripts 目录,开 Requests test.mq5 编译跑一遍,看 MT5 终端里哪些方法返回了非 0 错误码,再决定要不要改参数接自己的服务端。