开发具有 RestAPI 集成的 MQL5 强化学习代理(第 1 部分):如何在 MQL5 中使用 RestAPI·进阶篇
🔌

开发具有 RestAPI 集成的 MQL5 强化学习代理(第 1 部分):如何在 MQL5 中使用 RestAPI·进阶篇

(2/3)· 多数 EA 困在终端本地数据里,本文拆开 RestAPI 如何让 MT5 与外脑实时对话

含代码示例实战向 第 2/3 篇
不少交易者给 EA 加逻辑时只盯 K 线和指标,却忽略行情之外的因子。其实通过 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 拿到的报价仅作参考,杠杆品种波动剧烈,实盘接入前先在模拟盘跑通错误分支。

MQL5 / C++
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 行情只可作参考,不可作为下单唯一依据。

MQL5 / C++
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;
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到外部信号与本地策略的叠加视图,你只需判断要不要接进 EA。

常见问题

RestAPI 走标准 HTTP 协议,能与远端服务解耦通信,不依赖终端所在机器的文件结构,更适合接云侧强化学习代理返回的实时动作。
可以,小布盯盘的品种页支持把外部接口返回字段映射成看板,你能快速比对它和历史行情的吻合度,不必自己写回测脚本。
SOAP 基于 XML 冗余大、解析重,RestAPI 用 JSON 体积小、延迟低,对秒级风控类指令更友好,但外汇贵金属本身高杠杆高风险,接口故障也可能放大亏损概率。
按前文脉络,第 5 节大概率落在实际请求封装或错误重试机制,属于把前面架构原则转成 MQL5 可调用函数的过渡,完整代码见同系列实战篇。