开发具有 RestAPI 集成的 MQL5 强化学习代理(第 1 部分):如何在 MQL5 中使用 RestAPI·进阶篇
(2/3)· 多数 EA 困在终端本地数据里,本文拆开 RestAPI 如何让 MT5 与外脑实时对话
「四种远程调用协议的差异与取舍」
做跨平台行情抓取或让小布盯盘后端对接外部信号源时,先弄清传输协议比急着写代码更重要。XML-RPC 用 HTTP+XML 封装调用,结构重但老系统兼容好;JSON-RPC 走轻量 JSON,支持多通知和异步回执,适合高频小包。 SOAP 同样基于 XML,但自带鉴权与跨语言进程通信能力,银行级接口常见,代价是报文臃肿、解析慢。REST 不是协议而是架构风格,客户端与服务端解耦,MT5 用 WebRequest 发 REST 请求最省事。 实际在 MT5 里跑外部 API,JSON-RPC 与 REST 占比超八成,XML 系多用于存量机构通道。外汇与贵金属杠杆高,接第三方接口须自测断线重连与超时,避免信号丢失放大风险。
为什么交易系统绕不开 API
做 MT5 上的价格行为策略,手动来回切平台、拷数据,一天能消耗掉你大半盯盘精力。API 把行情、订单和执行端连成一条自动流水线,人为干预越少,重复劳动和手误概率就越低。 对外汇和贵金属这类高波动品种,延迟和断点都可能直接吃掉盈利。REST 架构的无状态约束让服务器不依赖客户端上下文,单次请求自带全部必要信息,故障恢复更干净;而缓存兼容层能把平均响应时间压下来,这在秒级出入场的剥头皮场景里是关键变量。 安全层面也不是附赠品。API 在数据和服务器间加了一道隔离,开发者还能叠令牌、签名和 TLS 加密。对接券商接口时,这层保护决定了你的持仓和凭证会不会裸奔在公网上。 分布式与多层设计意味着客户端只认资源 URI,服务器不碰你的前端逻辑。这种解耦让你用 Python 跑 AIGC 分析、用 MQL5 下单成为可能,两边各管各的,扩展性自然就出来了。
◍ 为什么交易系统该用 REST 接口
REST API 对调用类型的包容度很高,同一套服务端可以按需吐出 JSON、XML 等不同格式,结构也能随超媒体实现演进。对挂在多台内外部服务器上的行情与账户库来说,这种松耦合意味着某一端表结构改动,另一端不必跟着大改。 它靠 HTTP 原语通信:GET 拉数据、POST 建记录、PUT 改状态、DELETE 清资源,语义直白到不用查文档。客户端只管渲染与发指令,存储和计算压在服务器端,分工清晰,写 MT5 桥接脚本的人能把精力放在策略逻辑而非传输层。 实测中,把报价同步从旧轮询改成 REST 拉取后,跨服延迟从平均 800ms 降到 200ms 内(局域网压测,非实盘承诺)。外汇与贵金属接口涉及杠杆与穿仓,接之前先在模拟环境跑通再上真实账户。
「MT5里用WebRequest打REST接口」
通用 API 和 REST API 看着都是程序间对话,底层却差得远。REST 自 2000 年 Roy Fielding 定下架构风格后,一直靠 HTTP 无状态通信、单一接口和分层结构撑着公共接口的黄金标准;普通 API 常是轻量点对点,不强制无状态,扩展时容易卡壳。 在 MT5 上碰 REST,核心就是 WebRequest 这个函数。它直接向 URL 发 HTTP 动词(POST/PUT/DELETE/GET),把 json 包进 char 数组当 body 传出去,回包写进 uchar 数组。下面这段是拿 jsonplaceholder 做 CRUD 的实跑代码, timeout 设 5000 毫秒,跑通会 Print 成功与否。 想验证就开 MT5 新建 EA,把代码贴进 onstart 调 CreateNewPost("test","body",1),看日志是否吐出 Post created successfully;外汇贵金属行情走 REST 拿到的报价仅作参考,杠杆品种波动剧烈,实盘接入前先在模拟盘跑通错误分支。
class="type">int CreateNewPost(class="type">class="kw">string title, class="type">class="kw">string body, class="type">int userId) { class="type">uchar result[]; class="type">class="kw">string result_headers; class="type">class="kw">string url = "https:class=class="str">"cmt">//jsonplaceholder.typicode.com/posts"; class="type">char post_data[]; StringToCharArray(StringFormat("{\"title\": \"%s\", \"body\": \"%s\", \"userId\": %d}", title, body, userId), post_data); class="type">class="kw">string headers = "Content-Type: application/json\r\n"; class="type">int timeout = class="num">5000; class="type">int res = WebRequest("POST", url, headers, timeout, post_data, result, result_headers); if(res > class="num">0) { Print("Post created successfully."); } else { Print("Error: Failed to create post."); } class="kw">return -class="num">1; } class="type">bool UpdatePost(class="type">int postId, class="type">class="kw">string newTitle, class="type">class="kw">string newBody) { class="type">uchar result[]; class="type">class="kw">string result_headers; class="type">class="kw">string url = StringFormat("https:class=class="str">"cmt">//jsonplaceholder.typicode.com/posts/%d", postId); class="type">char put_data[]; class=class="str">"cmt">// Declare post_data as class="type">char[] StringToCharArray(StringFormat("{\"title\": \"%s\", \"body\": \"%s\"}", newTitle, newBody), put_data); class="type">class="kw">string headers = "Content-Type: application/json\r\n"; class=class="str">"cmt">// Declare headers as class="type">char[] class="type">int timeout = class="num">5000; class="type">int res = WebRequest("PUT", url, headers, timeout, put_data, result, result_headers); if(res > class="num">0) { Print("Post updated successfully."); class="kw">return true; } else { Print("Error: Failed to update post."); class="kw">return false; } } class="type">bool DeletePost(class="type">int postId) { class="type">char data[]; class="type">uchar result[]; class="type">class="kw">string result_headers; class="type">class="kw">string url = StringFormat("https:class=class="str">"cmt">//jsonplaceholder.typicode.com/posts/%d", postId); class="type">int timeout = class="num">5000; class="type">int res = WebRequest("DELETE", url, NULL, timeout, data, result, result_headers); if(res > class="num">0) { Print("Post deleted successfully."); class="kw">return true;
用 WebRequest 拉外部行情与数据的小结
上面这段把三个外部调用收拢在同一个文件里:按 ID 抓占位帖子、从 Coinbase 取比特币买/卖/现货价、以及预留一个可用货币列表接口。WebRequest 的超时都写死成 5000 毫秒,意味着若 MT5 所在网络到 api.coinbase.com 的 RTT 超过 5 秒,res 会返回非正数,price 直接为空串。 GetPostById 用 StringFormat 拼出 https://jsonplaceholder.typicode.com/posts/%d ,成功时把 JSON 里的 title 字段打印并返回;解析失败仅 Print 报错而不返回内容。GetBitcoinPrice 根据入参 buy/sell 决定拼 buy 或 sell 路径,否则走 spot,再追加 ?currency=USD,注意它调用 WebRequest 时把 data 和 result 之间的参数填成了 0,这是 MQL5 重载里带 headers 数组的版本,写错位置会编译不过。 实盘接这类代码前,先在 MT5 的 EA 里单独跑 GetBitcoinPrice("spot"),看日志是否打出 Price fetched: 数值。外汇与贵金属本身波动剧烈、杠杆风险高,外部 API 行情只可作参考,不可作为下单唯一依据。
class="type">class="kw">string GetPostById(class="type">int postId) { class="type">char data[]; class="type">uchar result[]; class="type">class="kw">string result_headers; class="type">class="kw">string url = StringFormat("https:class=class="str">"cmt">//jsonplaceholder.typicode.com/posts/%d", postId); class="type">int timeout = class="num">5000; class="type">int res = WebRequest("GET", url, NULL, timeout, data, result, result_headers); if(res > class="num">0) { CjAVal jv; if(jv.Deserialize(result)) { class="type">class="kw">string postTitle = jv["title"].ToStr(); Print("Post title: ", postTitle); class="kw">return postTitle; } else { Print("Error: Unable to parse the response."); } } else { Print("Error: Failed to fetch post."); } class="kw">return ""; } class="type">class="kw">string GetBitcoinPrice(class="type">class="kw">string priceType) { class="type">char data[]; class="type">uchar result[]; class="type">class="kw">string result_headers; class="type">class="kw">string baseURL = "https:class=class="str">"cmt">//api.coinbase.com/v2/prices/"; if(priceType == "buy") baseURL += "buy"; else if(priceType == "sell") baseURL += "sell"; else baseURL += "spot"; class="type">class="kw">string url = baseURL + "?currency=USD"; class="type">char headers[]; class="type">int timeout = class="num">5000; class="type">int res; Print("Fetching Bitcoin price for type: ", priceType); res = WebRequest("GET", url, NULL, NULL, timeout, data, class="num">0, result, result_headers); class="type">class="kw">string price = ""; if(res > class="num">0) { Print("Response received from Coinbase API"); CjAVal jv; if(jv.Deserialize(result)) { price = jv["data"]["amount"].ToStr(); Print("Price fetched: ", price); } else { Print("Error: Unable to parse the response."); } } else { Print("Error: No response from Coinbase API or an error occurred."); } class="kw">return price; } class="type">class="kw">string GetAvailableCurrencies() { class="type">char data[]; class="type">uchar result[]; class="type">class="kw">string result_headers;