构建新兴的社交技术, 第二部分: 编制 MQL5 的 REST 客户端(基础篇)
📘

构建新兴的社交技术, 第二部分: 编制 MQL5 的 REST 客户端(基础篇)

第 1/3 篇

◍ 用 MQL5 手搓一个 REST 客户端

在 MT5 里直接对接外部 Web 服务,是社交交易与跨平台信号聚合的常见需求。MQL5 标准库并未内置高级 HTTP 客户端,但借助 WinAPI 的 InternetOpen 系列函数,可以在 EA 或脚本中发起 REST 请求。

  • 年 9 月 11 日发布的这篇技术文,在 MetaTrader 5 专栏获得了 2352 次浏览,说明当时社区对「终端内直连 REST」的渴求很真实。即便放在现在,原生实现依然比依赖第三方 DLL 更可控。

核心思路是:先调用 InternetOpen 建立会话,再用 InternetOpenUrl 打目标地址,最后用 InternetReadFile 循环读响应体。外汇与贵金属行情接口若走这套,需注意网络延迟与断线重连,这类品种杠杆高、滑点风险大,任何自动拉取都只是辅助判断。

「从架构到客户端:MQL5 怎么把信号送出去」

上一阶段我们已经搭好一套社交决策支持系统(SDSS)的骨架:MT5 客户端负责把 EA 的自动决策发往服务器,另一端是用瘦 PHP 框架写的 Twitter 应用,信号进 MySQL 后再推给相关人。它的核心目的不是全自动下单,而是把机器产生的交易信号摊在大量专业观众面前,由人来做最终执行判断。 本篇要做的,是用 MQL5 把这套体系的客户端真正写出来。我们会顺带比较几种可行的替代实现路径,把各自的利弊摊开,再把这些零散模块拼起来,最终塑出能接收 EA 信号的 PHP REST API。 想立刻动手验证的话,打开 MT5 的 MQL5 编辑器新建 EA,先确认你本地能向一个测试用 REST 接口 POST 数据——这一步通了,后面整条信号链路才算立住。外汇与贵金属市场波动剧烈、杠杆风险高,任何信号展示系统都只是辅助,人工决策仍需自行承担后果。

用 OnTimer 把信号推给远端 API

在 MT5 里做信号中转,最省事的办法是把 HTTP 通信交给 MQL5 自带的 WebRequest,而不是自己写底层控件。让 MetaQuotes 的语言特性去管会话,比手写 wininet.dll 方案更稳——后者一旦终端升级,调用就可能直接崩。 下面这段客户端每 10 秒触发一次 OnTimer,向远端接口 POST 一条 URL 编码的信号。注意 WebRequest 默认按 HTML 表单发,所以我们把信号拼成 id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281& 这种格式,服务端再转成内部对象。

MQL5 / C++
class="macro">#class="kw">property copyright "Author: laplacianlab, CC Attribution-Noncommercial-No Derivate class="num">3.0"
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#class="kw">property description "Simple REST client built on the OnTimer event for learning purposes."
class="type">int OnInit()
  {
  EventSetTimer(class="num">10);
  class="kw">return(class="num">0);
  }

class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
  }

class="type">void OnTimer()
  {
class=class="str">"cmt">//--- REST client&class="macro">#x27;s HTTP vars
  class="type">class="kw">string uri="http:class=class="str">"cmt">//api.laplacianlab.com/signal/add";
  class="type">char post[];
  class="type">char result[];
  class="type">class="kw">string headers;
  class="type">int res;
  class="type">class="kw">string signal = "id_ea=class="num">1&symbol=AUDUSD&operation=BUY&value=class="num">0.9281&";
  StringToCharArray(signal,post);
class=class="str">"cmt">//--- reset last error
  ResetLastError();
class=class="str">"cmt">//--- post data to REST API
  res=WebRequest("POST",uri,NULL,NULL,class="num">50,post,ArraySize(post),result,headers);
class=class="str">"cmt">//--- check errors
  if(res==-class="num">1)
    {
      Print("Error code =",GetLastError());
      MessageBox("Add address &class="macro">#x27;"+uri+"&class="macro">#x27; in Expert Advisors tab of the Options window","Error",MB_ICONINFORMATION);
    }
  else
    {
      class=class="str">"cmt">//--- successful
      Print("REST client&class="macro">#x27;s POST: ",signal);
      Print("Server response: ",CharArrayToString(result,class="num">0,-class="num">1));
    }
  }
逐行拆解:EventSetTimer(10) 设定 10 秒周期;uri 是信号接收地址,需先在 MT5 选项→EA 标签里把该域名加入允许列表,否则 WebRequest 返回 -1。signal 字符串里 id_ea=1 关联数据库里的 EA 编号,symbol 和 value 就是品种与价格,例如样例推的是 AUDUSD 买价 0.9281。 服务端不是来者不拒。样例里 PHP 的 canTweet 方法会拦高频剥头皮,相当于把每秒海量 POST 视作 HTTP 攻击;Apache 的 mod_evasive 也能在配置层限制每秒请求数。日志里 15:43:45.363 那条 POST 成功,说明域名白名单和发送节奏都对了。 外汇与贵金属杠杆高、滑点大,这类信号转发只解决通信,不预示任何方向。你把这个定时器框架套进自己的 EA,把 signal 里的 operation 和 value 换成实际下单逻辑,就能在 MT5 里跑通一套轻量广播。

MQL5 / C++
class="macro">#class="kw">property copyright "Author: laplacianlab, CC Attribution-Noncommercial-No Derivate class="num">3.0"
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class="macro">#class="kw">property description "Simple REST client built on the OnTimer event for learning purposes."
class="type">int OnInit()
  {
  EventSetTimer(class="num">10);
  class="kw">return(class="num">0);
  }

class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
  }

class="type">void OnTimer()
  {
class=class="str">"cmt">//--- REST client&class="macro">#x27;s HTTP vars
  class="type">class="kw">string uri="http:class=class="str">"cmt">//api.laplacianlab.com/signal/add";
  class="type">char post[];
  class="type">char result[];
  class="type">class="kw">string headers;
  class="type">int res;
  class="type">class="kw">string signal = "id_ea=class="num">1&symbol=AUDUSD&operation=BUY&value=class="num">0.9281&";
  StringToCharArray(signal,post);
class=class="str">"cmt">//--- reset last error
  ResetLastError();
class=class="str">"cmt">//--- post data to REST API
  res=WebRequest("POST",uri,NULL,NULL,class="num">50,post,ArraySize(post),result,headers);
class=class="str">"cmt">//--- check errors
  if(res==-class="num">1)
    {
      Print("Error code =",GetLastError());
      MessageBox("Add address &class="macro">#x27;"+uri+"&class="macro">#x27; in Expert Advisors tab of the Options window","Error",MB_ICONINFORMATION);
    }
  else
    {
      class=class="str">"cmt">//--- successful
      Print("REST client&class="macro">#x27;s POST: ",signal);
      Print("Server response: ",CharArrayToString(result,class="num">0,-class="num">1));
    }
  }

◍ 服务端时间窗拦截了重复发单

上面这段 MT5 日志来自一个挂在 EURUSD H1 图表上的 RESTClient 脚本,它不断向远端服务提交 AUDUSD 的 BUY 信号,价格锚定 0.9281,EA 编号 id_ea=1。从 15:43:54 到 15:45:04,每隔约 10 秒就 POST 一次同样内容,但服务端回了 11 次 Please wait until the time window has elapsed.,只在 15:43:45 和 15:44:54 两次真正写入了 id 77、id 78 两条信号。 服务端并不是收到就存,而是用了一个最小时间间隔闸门。底下那段 PHP 路由 /signal/add 里的 canTweet($tweeterer->getLastSignal(1)->created_at, '1 minute') 就是关键:它拿上一条信号时间和当前比,若不足 1 分钟就直接拒绝。日志里两次成功插入相差 69 秒,正好跨过了这个窗口。 别把这种限流当摆设。外汇和贵金属波动快、滑点多,若 EA 在 10 秒级疯狂重发同一方向单,服务端拦得住,但本地已经吃掉来回延迟和连接开销;把重发间隔在 EA 侧就设成 ≥60 秒,比等远端驳回更干净。 下面这段是服务端接收信号的接口骨架,注意注释里写明了「prevent hyperactive scalpers」——限流本来就是防过度交易的: /**

  • REST method.
  • Adds and tweets a new trading signal.

*/ $app->post('/signal/add', function() { $tweeterer = new Tweeterer(); // This condition is a simple mechanism to prevent hyperactive scalpers if ($tweeterer->canTweet($tweeterer->getLastSignal(1)->created_at, '1 minute')) { $signal = (object)($_POST); $signal->id = $tweeterer->addSignal(1, $signal); $tokens = $tweeterer->getTokens(1); $connection = new TwitterOAuth( API_KEY, API_SECRET, $tokens->access_token, $tokens->access_token_secret); $connection->host = "https://api.twitter.com/1.1/"; $ea = new EA();

MQL5 / C++
class=class="str">"cmt">/**
* REST method.
* Adds and tweets a new trading signal.
*/
$app->post(&class="macro">#x27;/signal/add&class="macro">#x27;, function() {
    $tweeterer = new Tweeterer();
    class=class="str">"cmt">// This condition is a simple mechanism to prevent hyperactive scalpers
    if ($tweeterer->canTweet($tweeterer->getLastSignal(class="num">1)->created_at, &class="macro">#x27;class="num">1 minute&class="macro">#x27;))
    {
        $signal = (object)($_POST);
        $signal->id = $tweeterer->addSignal(class="num">1, $signal);
        $tokens = $tweeterer->getTokens(class="num">1);
        $connection = new TwitterOAuth(
            API_KEY,
            API_SECRET,
            $tokens->access_token,
            $tokens->access_token_secret);
        $connection->host = "https:class=class="str">"cmt">//api.twitter.com/class="num">1.1/";
        $ea = new EA();

「信号推送的频控与服务器防刷配置」

把 EA 信号往外推时,最容易被忽略的是频率控制。上面这段 PHP 逻辑用 canTweet() 卡住了最小发声间隔:1 分钟窗口对应 60 秒、1 小时对应 3600 秒、1 天对应 86400 秒,未到窗口就直接抛异常而不是静默吞掉,能避免同一条黄金突破信号在 MT5 触发后被刷成几十条推文。 服务器端也不能裸奔。mod_evasive20 那段配置把 DOSPageCount 设为 2、DOSSiteCount 设为 50,页级间隔和站级间隔都是 1 秒,命中后封锁 60 秒——这对接了公开 API 的信号中转站算是基础门槛,外汇和贵金属行情波动剧烈,接口被刷会影响你跟单的时效性,这类资产本身杠杆高风险也大。 真正落地时,把时间窗从『1 hour』改成你策略的平均持仓周期更合理。例如剥头皮 EA 平均 20 分钟平仓,硬卡 3600 秒就会漏掉后半段信号;直接调 case 里的数值或加一个『20 minutes』分支就能在 MT5 外接脚本里验证效果。

常见问题

核心需要三块:组装请求参数、用 WebRequest 发 HTTP 请求、用 OnTimer 定时触发。先跑通单次推送再加重试和频控。
多是服务端时间窗没拦住或本地没记已发状态。建议在客户端用静态变量标记已推送 ticket,服务端也按时间窗去重。
可以。小布能接你的推送日志做异常检测,漏发、重复发、接口超时都会直接弹提醒,不用你自己盯后台。
按信号事件触发优于固定高频轮询;若用定时,建议不低于 5 秒一次,并在服务端配防刷白名单与限流。
要做。收到拦截码后停发当前周期,下一个时间窗再试,避免死循环重试触发更严的封禁。