在 MQL5 中实现其他语言的实用模块(第 02 部分):构建受 Python 启发的 REQUESTS 库(基础篇)
◍ 在 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 之类,否则服务端可能拒收或解析错。外汇与贵金属行情接口调用涉及网络与第三方服务,存在请求失败、延迟与数据异常的高风险,任何自动化发送都应先在模拟环境验证。
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 封装类:
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 跑一遍核对。
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 这类回显站点验证完再换生产地址。
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()); }