在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库·综合运用
📘

在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库·综合运用

第 3/3 篇

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,实盘触发信号时就要考虑异步或本地缓存。

MQL5 / C++
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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Reason: OK KE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; URL: https:<span class="comment">//httpbin.org/post</span> GR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OK: <span class="macro">true</span> LD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Elapsed Time (ms): <span class="number">1578</span> GL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</spa

正文

NL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="string">"files"</span>: {}, QJ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="string">"form"</span>: {}, PS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="string">"headers"</span>: { DJ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="number">15</span>:<span class="number">32</span>:<span class="number">13.093</span>&nbsp;&nbsp;&nbsp;&nbsp;Requests test (XAUUSD,D1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="string">"Accept"</span>: <span class="string">"*/*"</span>, HO&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">0</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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,把这段直接丢进脚本跑,就能在专家日志看到回显耗时与响应体。 黄金外汇品种涉及外部网络请求时波动与断连风险偏高,这类凭证传输仅适合本地沙盒或测试域名验证,别直接挂实盘地址。

MQL5 / C++
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。 外汇与贵金属行情波动剧烈、杠杆风险高,这类外部请求只适合做信号备份或模型调用,别拿它当下单通道。

MQL5 / C++
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);
  }
逐行拆解: void OnStart() —— 脚本入口,MT5 加载即执行。 string json = "{\"update\": true}"; —— 构造待发送的 JSON 字符串,转义双引号。 CResponse response = CSession::put("https://httpbin.org/put", json, "", 5000, true); —— 调 PUT 方法,目标 URL、正文、空 header、超时 5000 毫秒、true 表示打印详细错误。 Print("Reason: ", response.reason); —— 输出状态码短语,正常为 OK。 Print("Response: ",response.text); —— 输出服务端返回的全文,便于核对。 开 MT5 新建脚本粘入代码,跑在 XAUUSD 的 D1 上,就能复现这段回显。

MQL5 / C++
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 方便肉眼看回执。

MQL5 / C++
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,证明服务端接受了删除指令。 黄金品种波动剧烈、杠杆风险高,任何对外请求都应在模拟盘先跑通;实盘调用第三方接口可能因网络或风控导致指令丢失,概率上不保证一定生效。

MQL5 / C++
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 与请求头是否被经纪商环境篡改。

MQL5 / C++
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 才走多部件分支,否则照常发文本。

MQL5 / C++
CResponse CSession::request(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> method,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> url,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> data,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> &amp;files[],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <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>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> timeout = <span class="number">class="num">5000</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">bool</span> is_json=<span class="macro">true</span>)
{
&nbsp;&nbsp; <span class="keyword">class="type">char</span> result[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">string</span> result_headers;
&nbsp;&nbsp; <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);">&nbsp;&nbsp; <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>&nbsp;&nbsp; CArrayChar final_body; <span class="comment">class=class="str">"cmt">//Final body class="type">uchar array</span>
&nbsp;&nbsp; CResponse response; <span class="comment">class=class="str">"cmt">//a structure containing various response fields</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Append user headers</span>
&nbsp;&nbsp; <span class="keyword">if</span> (headers != <span class="class="type">class="kw">string">""</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;temp_headers += headers;
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> use_multipart = <span class="functions">ArraySize</span>(files) &gt; <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>
&nbsp;&nbsp; <span class="keyword">if</span> (use_multipart) <span class="comment">class=class="str">"cmt">// If multipart, assemble full body(JSON + files)</span>
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- JSON part(or form data)</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="functions">StringLen</span>(data) &gt; <span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">string</span> json_data = <span class="class="type">class="kw">string">""</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span> (is_json) <span class="comment">class=class="str">"cmt">//if Json data is given alongside the files </span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CJAVal js(<span class="macro">NULL</span>, jtUNDEF);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (js.Deserialize(data, <span class="macro">CP_UTF8</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; js.Serialize(json_data); <span class="comment">class=class="str">"cmt">//Serialize the JSON data</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <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>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; json_part += <span class="class="type">class="kw">string">"Content-Disposition: form-data; name=\"metadata\"\r\n"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; json_part += <span class="class="type">class="kw">string">"Content-Type: application/json\r\n\r\n"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 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 前。

MQL5 / C++
  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,调用方据此判断接口是否回了结构化数据。

MQL5 / C++
  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 会落空返回默认型。

MQL5 / C++
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 都有失效可能。

MQL5 / C++
  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 解析。

MQL5 / C++
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 两行。黄金外汇接口调用波动大、高风险,配额耗尽会直接断图,建议本地缓存上一张成功响应。

MQL5 / C++
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 日志里排错。

MQL5 / C++
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 用法去对接自己的外汇/贵金属数据源。 外汇与贵金属品种波动剧烈、杠杆风险高,任何自动化取数仅作分析辅助,不代表行情方向判断。

MQL5 / C++
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,需注意网络抖动和券商环境限制,实盘使用前先在模拟盘验证连通性与失败重试逻辑。

MQL5 / C++
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 这类公开回显站验证。

MQL5 / C++
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 生命周期。

MQL5 / C++
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 才稳妥,这类品种杠杆高、断连重拼可能错过报价窗口。

MQL5 / C++
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,但服务端解析时得兼容。

MQL5 / C++
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 错误码,再决定要不要改参数接自己的服务端。

常见问题

用脚本里构造请求时把 Header 数组打印到日志,或在抓包工具里看本地出站连接,能确认 Content-Type、Authorization 等字段是否按预期发送。
写个脚本用内置 HTTP 函数发 POST 到测试地址,带简单 JSON 体和超时设置,看返回码和响应体即可快速验证接口是否存活。
小布盯盘的 AIGC 可内置这类诊断,打开对应品种页就能看到接口调用异常提醒,把重复验证劳动交给它,你只管决策。
凭证放 Body 而非 URL,头里加 Authorization 且走 HTTPS,发完立即清掉脚本里的明文变量,避免本地日志泄露。
多数情况是 Header 没声明 Content-Type: application/json,或 JSON 拼错引号,先打印发出体和返回错误码对照接口文档排查。