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

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

第 1/3 篇

◍ 在 MQL5 里拼一个类 Python 的 HTTP 请求模块

MQL5 原生只提供 WebRequest 这一个偏底层的网络函数,要在 EA 或脚本里稳定调外部 API,往往得反复写样板代码。借鉴 Python requests 的思路,可以把常用动作封装成一套独立 MQL5 模块,让调用层只关心 URL 和参数。 该系列第 02 部分给出的实现,目标就是抹平 MQL5 与 Python 在发起 Web 请求时的体验落差。基础能力覆盖:发出 GET/POST 类请求、在不同函数间复用请求逻辑、向 Web 上传文件、从 Web 接收并下载文件。 文章发布于 2026 年 3 月 2 日,截至收录时页面显示 395 次查看、0 条评论,说明这类底层通信封装在普通交易者里关注度还不算高,但对做跨平台信号同步的人属于硬需求。外汇与贵金属行情受消息面驱动明显,用 Web 请求拉外部数据须自行承担接口失效与高波动风险。

「MT5 原生 WebRequest 的痛点与借鉴对象」

MT5 可以直接对外发 HTTP 请求,这是 MQL5 里极实用的能力:能连外部站点、私有服务器、交易类应用,拉数据或推通知都行。底层靠 WebRequest 函数,支持 POST 发信息、GET 取数据、PATCH 改库、PUT 更新值等标准动作。 但这个函数用起来并不轻巧。发一个简单请求就得手写方法、报头、数据编解码,还要硬编码收发处理逻辑,工作量常被低估。 对比 MQL5 之外的生态,Python 的 requests 模块号称“面向人类的 HTTP”,不用手拼查询串、不用自己编码 PUT/POST 体,非技术背景也能顺手调用。本文目标就是给 MQL5 造一个语法和能力接近 requests 的轻量封装,让发请求像在 Python 里一样省事。 外汇与贵金属市场高波动、高杠杆,任何自动外联逻辑都先在小资金或模拟环境验证再上实盘。

在 MT5 里用一套函数发 JSON 和表单数据

MQL5 原生 WebRequest 偏底层,自己包一层 CSession::request 后,能像 Python requests 那样统一收发 HTTP 内容。设计上只留一个 data 参数收发送体,再用布尔 is_json 区分类型:设为 true 时内部先把 data 序列化成标准 JSON,并自动把响应头改成 Content-Type: application/json;非 JSON 数据则完全靠调用方显式给 Content-type。 返回结构用 CResponse 承接,关键字段可直接拿来做判断:status_code 是服务器 HTTP 状态码(200 表示 OK,404 表示未找到);ok 在状态码小于 400 时为 true;elapsed 是耗时(毫秒);text 是原始响应体字符串,json 字段在解析成功后才返回 CJAVal 对象,content[] 则是原始字节数组,适合二进制。 实测要把 https://httpbin.org 加进 MT5 的允许 URL 列表,否则请求会被终端拦截。下面这段是 Python 原型与 MQL5 封装的签名对照,注意 MQL5 版默认 timeout=5000 毫秒,且把 params/files/auth 等高级参数砍掉以降低复杂度。 发非 JSON 表单数据时,务必在 headers 里手写 Content-type: application/x-www-form-urlencoded 之类,否则服务端可能拒收或解析错。外汇与贵金属行情接口调用涉及网络与第三方服务,存在请求失败、延迟与数据异常的高风险,任何自动化发送都应先在模拟环境验证。

MQL5 / C++
def request(
    method: str | bytes,
    url: str | bytes,
    *,
    params: _Params | None = ...,
    data: _Data | None = ...,
    headers: _HeadersMapping | None = ...,
    cookies: CookieJar | _TextMapping | None = ...,
    files: _Files | None = ...,
    auth: _Auth | None = ...,
    timeout: _Timeout | None = ...,
    allow_redirects: class="type">bool = ...,
    proxies: _TextMapping | None = ...,
    hooks: _HooksInput | None = ...,
    stream: class="type">bool | None = ...,
    verify: _Verify | None = ...,
    cert: _Cert | None = ...,
    json: Any | None = None
) -> Response
CResponse CSession::request(class="kw">const class="type">class="kw">string method, class=class="str">"cmt">//HTTP method GET, POST, etc
                            class="kw">const class="type">class="kw">string url, class=class="str">"cmt">//endpoint url
                            class="kw">const class="type">class="kw">string data = "", class=class="str">"cmt">//The data you want to send if the method is POST or PUT
                            class="kw">const class="type">class="kw">string headers = "", class=class="str">"cmt">//HTTP headers
                            class="kw">const class="type">int timeout = class="num">5000, class=class="str">"cmt">//Request timeout(milliseconds)

◍ 把字典塞进 POST 请求前先转 JSON

在 MT5 里用 WebRequest 打外部 API,最容易被坑的一步是:传参前没把数据格式和请求头对齐。上面这段逻辑干的事很直接——如果 is_json 为 true,就先把传入的 data 反序列化成 CJAVal 对象,再序列化成标准 JSON 串,并把 Content-Type 强制改成 application/json。 注意 temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json") 这行。很多自建 REST 接口只认 application/json,漏掉这头就会返回 400 或空响应。调试阶段用 MQLInfoInteger(MQL_DEBUG) 把最终头打印出来,能省掉一半抓包时间。 数据转换用 StringToCharArray 以 CP_UTF8 落字节数组,失败直接 return 空 response,不会让 EA 卡死在发送环节。请求耗时靠 GetTickCount() 前后差值填进 response.elapsed,实盘跑外汇或贵金属信号接口时,这个毫秒数若经常超过 timeout 设定,就要警惕券商网络或 API 限频带来的滑点风险。 下面这段是原文核心函数片段,逐行拆完你就能直接抄进自己的 HTTP 封装类:

MQL5 / C++
class="kw">const class="type">bool is_json=true) class=class="str">"cmt">//Checks whether the given data is in JSON format
{
 class="type">char data_char[];
 class="type">char result[];
 class="type">class="kw">string result_headers;
 class="type">class="kw">string temp_data = data;

 CResponse response; class=class="str">"cmt">//a structure containing various response fields
class=class="str">"cmt">//--- Managing the headers
 class="type">class="kw">string temp_headers = m_headers;
 if (headers != "") class=class="str">"cmt">// If the user provided additional headers, append them
    temp_headers += headers;
class=class="str">"cmt">//--- Updating the headers with the information received

 if(is_json) class=class="str">"cmt">//If the information parsed is 
   {
     class=class="str">"cmt">//--- Convert dictionary to JSON class="type">class="kw">string

     CJAVal js(NULL, jtUNDEF);
     class="type">bool b = js.Deserialize(data, CP_UTF8);

     class="type">class="kw">string json;
     js.Serialize(json); class=class="str">"cmt">//Get the serialized Json outcome
     temp_data = json; class=class="str">"cmt">//Assign the resulting serialized Json to the temporary data array

     class=class="str">"cmt">//--- Set "Content-Type: application/json"

     temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json");
     if (MQLInfoInteger(MQL_DEBUG))
       printf("%s: %s",__FUNCTION__,temp_headers);
   }
  else
    {
      temp_headers = UpdateHeader(temp_headers, headers);
      if (MQLInfoInteger(MQL_DEBUG))
        printf("%s: %s",__FUNCTION__,temp_headers);
    }

class=class="str">"cmt">//--- Convert data to byte array(for POST, PUT, etc.)
 if (StringToCharArray(temp_data, data_char, class="num">0, StringLen(temp_data), CP_UTF8)<class="num">0) class=class="str">"cmt">//Convert the data in a class="type">class="kw">string format to a class="type">uchar
   {
     printf("%s, Failed to convert data to a Char array. Error = %s",__FUNCTION__,ErrorDescription(GetLastError()));
     class="kw">return response;
   }
class=class="str">"cmt">//--- Perform the WebRequest
 class="type">uint start = GetTickCount(); class=class="str">"cmt">//starting time of the request

   class="type">int status = WebRequest(method, url, temp_headers, timeout, data_char, 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);

   class="type">class="kw">string results_string = CharArrayToString(result);
   response.text = results_string;

   CJAVal js;

「把 WebRequest 回包塞进统一响应结构」

在 MT5 里自己封装 HTTP 客户端时,最容易被忽略的是回包归一化:不同接口返回的字节流、状态码、cookie 必须收口到一个 struct 才方便后续逻辑判断。下面这段把 WebRequest 的结果填进 CResponse,状态码 200–399 才标记 ok,其余一律视为异常倾向。 响应结构先定义清楚字段,status_code 存整数、text 存原始串、json 用 CJAVal 解析、content 存裸字节、headers 与 cookies 存文本。这样 EA 里无论调行情 API 还是风控接口,拿到手的对象格式都一致。 反序列化失败时在调试模式打印提示,但生产环境静默跳过,避免日志刷屏。注意外汇与贵金属接口常带跳转,url 字段要保留最终地址,否则重试逻辑可能打错端点。 若服务端回的是 JSON,要把临时头里的 Content-Type 改成 application/json 再发出去;否则走默认 UpdateHeader 合并。两者都建议在 MQL_DEBUG 下打印临时头,方便你开 MT5 跑一遍核对。

MQL5 / C++
if (!js.Deserialize(result))
  if (MQLInfoInteger(MQL_DEBUG))
    printf("Failed to serialize data received from WebRequest");

response.json = js;
response.cookies = js["cookies"].ToStr();
response.status_code = status;
ArrayCopy(response.content, result);
response.headers = result_headers;
response.url = url;
response.ok = (status >= class="num">200 && status < class="num">400);
response.reason = WebStatusText(status); class=class="str">"cmt">// a custom helper for translating status codes
class="kw">return response;

class=class="str">"cmt">//--- Updating the headers with the information received
if(is_json) class=class="str">"cmt">//If the information parsed is
  {
    class=class="str">"cmt">//--- Convert dictionary to JSON class="type">class="kw">string
    CJAVal js(NULL, jtUNDEF);
    class="type">bool b = js.Deserialize(data, CP_UTF8);
    class="type">class="kw">string json;
    js.Serialize(json); class=class="str">"cmt">//Get the serialized Json outcome
    temp_data = json; class=class="str">"cmt">//Assign the resulting serialized Json to the temporary data array
    class=class="str">"cmt">//--- Set "Content-Type: application/json"
    temp_headers = UpdateHeader(temp_headers, "Content-Type", "application/json");
    if (MQLInfoInteger(MQL_DEBUG))
      printf("%s: %s",__FUNCTION__,temp_headers);
  }
  else
    {
      temp_headers = UpdateHeader(temp_headers, headers);
      if (MQLInfoInteger(MQL_DEBUG))
        printf("%s: %s",__FUNCTION__,temp_headers);
    }

class="kw">struct CResponse
  {
   class="type">int            status_code; class=class="str">"cmt">// HTTP status code(e.g., class="num">200, class="num">404)
   class="type">class="kw">string         text;        class=class="str">"cmt">// Raw response body as class="type">class="kw">string
   CJAVal         json;        class=class="str">"cmt">// Parses response as JSON
   class="type">uchar          content[];   class=class="str">"cmt">// Raw bytes of the response
   class="type">class="kw">string         headers;     class=class="str">"cmt">// Dictionary of response headers
   class="type">class="kw">string         cookies;     class=class="str">"cmt">// Cookies set by the server
  };

用 MT5 跑通一次 JSON 登录回传

在 MT5 里做远端信号接入,先得确认 HTTP 会话能带 JSON body 正常往返。下面这段脚本直接 POST 一个模拟账号密码到 httpbin,拿回 CResponse 结构里的字段,适合作为你接自己 AIGC 分析接口前的冒烟测试。 CResponse 里几个字段要盯紧:status_code 小于 400 时 ok 才为 true;elapsed 是本次响应耗时(毫秒);url 是重定向后的最终地址;reason 给的是文本状态如 "OK"。这些在调试外汇或贵金属行情外挂服务时,能帮你快速判断是网络层还是业务层出的问题。 实跑日志里能看到:在 XAUUSD 的 D1 图表上挂脚本,08:10:33.226 发出请求并自动带 Content-Type: application/json,到 08:10:34.578 收回,status_code = 200、reason = OK、ok = true,elapsed = 1343 ms。也就是说一次跨公网 JSON 往返在 1.3 秒量级,介意延迟的策略要把这个开销算进信号触发窗口。 贵金属和外汇品种接外部接口属于高风险操作,远端延迟或丢包都可能让盯盘逻辑误判,建议先在 httpbin 这类回显站点验证完再换生产地址。

MQL5 / C++
  class="type">class="kw">string                url;         class=class="str">"cmt">// Final URL after redirects
  class="type">bool                  ok;          class=class="str">"cmt">// True if status_code < class="num">400
  class="type">uint                  elapsed;     class=class="str">"cmt">// Time taken for the response in ms
  class="type">class="kw">string                reason;      class=class="str">"cmt">// Text reason(e.g., "OK", "Not Found")
  };
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::request("POST","https:class=class="str">"cmt">//httpbin.org/post", json, NULL, class="num">5000, true);
   
   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());
}

常见问题

可以封装一层类似Python requests的函数,把字典先转成JSON字符串再塞进WebRequest的body,统一收响应结构,避免每次手写header和解析。
把表单和JSON分别做成两个发送函数,POST前判断内容类型再选编码方式,回包统一用同一个响应结构接,调试时直接看状态码和文本。
小布可以替你跑这套请求封装和回传校验,把重复劳动交给它,你专注决策和策略逻辑即可。
把回包塞进统一响应结构,记录HTTP状态码和body文本,状态码非200或body为空就倾向判定为请求异常。
准备一个登录接口地址、转JSON的字典函数、POST发送函数、统一响应结构,然后打印状态码和回传文本确认即可。