一个使用命名管道在 MetaTrader 5 客户端之间进行通信的无 DLL 解决方案·进阶篇
🔌

一个使用命名管道在 MetaTrader 5 客户端之间进行通信的无 DLL 解决方案·进阶篇

(2/3)· 不用 DLL、不写文件,靠命名管道在多个 MT5 终端间跑通报价流

实战向进阶 第 2/3 篇

很多交易者想让不同 MT5 客户端共享实时报价,却还在用文件互写那套笨办法,延迟高还容易锁文件。其实系统自带的命名管道能直接打通进程,只是多数人被 DLL 门槛劝退。本篇接着把无 DLL 的管道通信落到可跑的代码结构上。

用命名管道在 MT5 里搬运字符串与报价

把 EA 和外部程序打通,命名管道是最直接的办法之一。下面这组方法封装了 ANSI 字符串和 MqlTick 报价的双向读写,开 MT5 建个 CNamedPipe 类就能直接抄。 WriteANSI 先调用 StringToCharArray 把 MQL5 的 Unicode 字符串压成 uchar 数组,再分两次 WriteFile:第一次写 int 类型的字节长度,第二次写真实数据,返回实际写入字节数。 ReadANSI 反过来,先读一个 int 拿到待收字节数,再按长度读 uchar 数组,最后 CharArrayToString 还原成 MQL5 字符串;若本次没读到数据(bytesRead 为 0)就返回空串。 WriteTick 和 ReadTick 专门搬 MqlTick 结构,用 MQLTICK_SIZE 常量控制传输体量,省去自己算字段偏移。外汇与贵金属市场高杠杆、报价跳变频繁,管道读写若不同步可能丢 tick,实盘前务必在模拟环境跑通。

MQL5 / C++
class="type">int CNamedPipe::WriteANSI(class="type">class="kw">string message)
  {
   class="type">int bytesToWrite, bytesWritten;
   class="type">uchar ANSIarray[];
   bytesToWrite = StringToCharArray(message, ANSIarray);
   WriteFile(hPipe,bytesToWrite,class="kw">sizeof(class="type">int),bytesWritten,class="num">0);
   WriteFile(hPipe,ANSIarray,bytesToWrite,bytesWritten,class="num">0);
   class="kw">return bytesWritten;
  }
class="type">class="kw">string CNamedPipe::ReadANSI(class="type">void)
  {
   class="type">class="kw">string ret;
   class="type">uchar ANSIarray[STR_SIZE];
   class="type">int bytesRead, bytesToRead;
   ReadFile(hPipe,bytesToRead,class="kw">sizeof(class="type">int),bytesRead,class="num">0);
   ReadFile(hPipe,ANSIarray,bytesToRead,bytesRead,class="num">0);
   if(bytesRead!=class="num">0)
      ret = CharArrayToString(ANSIarray);
   class="kw">return ret;
  }
class="type">int CNamedPipe::WriteTick(class="type">MqlTick &outgoing)
  {
   class="type">int bytesWritten;
   WriteFile(hPipe,outgoing,MQLTICK_SIZE,bytesWritten,class="num">0);
   class="kw">return bytesWritten;
  }
class="type">int CNamedPipe::ReadTick(class="type">MqlTick &incoming)
  {
   class="type">int bytesRead;
   ReadFile(hPipe,incoming,MQLTICK_SIZE,bytesRead,NULL);
   class="kw">return bytesRead;
  }

◍ 用命名管道把报价推给外部客户端

在 MT5 里做跨进程报价中转,最轻量的办法是命名管道。服务端脚本先建好管道并卡在连接等待上,客户端没连进来之前它只在日志里刷“等待客户端连接.”,不占行情线程。 客户端一旦连上,服务端进入内层循环不断 ReadTick(),每收到一次 tick 就把计数器 i 加 1 并 Print 出“N即时价收到.”。实测在 EURUSD 正常波动时段,一分钟内管道可能推几百到上千个 tick,具体量级取决于品种流动性和客户端消费速度。 客户端断开时,ReadTick 返回 false 且 GetError() 拿到 ERROR_BROKEN_PIPE,服务端打印断开日志、调用 Disconnect() 并 break 回外层,重新等下一个客户端。若外层循环发现全局变量 gvar0 存在就整体退出;没有 gvar0 时,只能去图表右键 Expert List 手动停掉脚本。 别把 gvar0 当摆设 想让服务端干净退出,在另一个脚本或终端里 GlobalVariableSet("gvar0",1) 比手动去 EA 列表找它更稳,尤其多图表挂着的时候。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                NamedPipeServer.mq5 |
class=class="str">"cmt">//|                 Copyright class="num">2010, Investeo.pl |
class=class="str">"cmt">//|                                                    http:/Investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------
class="macro">#class="kw">property copyright "Copyright class="num">2010, Investeo.pl"
class="macro">#class="kw">property link      "http:/Investeo.pl"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <CNamedPipes.mqh>
CNamedPipe pipe;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 交易程序初始函数                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">bool tickReceived;
   class="type">int i=class="num">0;
   if(pipe.Create()==true)
      while (GlobalVariableCheck("gvar0")==class="kw">false)
        {
         Print("等待客户端连接.");
         if (pipe.Connect()==true)
            Print("管道已连接");
         while(true)
           {
            do
              {
               tickReceived=pipe.ReadTick();
               if(tickReceived==class="kw">false)
                 {
                  if(GetError()==ERROR_BROKEN_PIPE)
                    {
                     Print("客户端从管道断开 "+pipe.Name());
                     pipe.Disconnect();
                     break;
                    }
                 } else i++;
               Print(IntegerToString(i) + "即时价收到.");
              } while(tickReceived==true);
            if (i>class="num">0)
              {
               Print(IntegerToString(i) + "即时价收到.");
               i=class="num">0;
              };

「管道断开时的退出判定」

在 MQL5 里用命名管道做跨程序通信时,最怕终端一侧突然退出。若服务端崩了,客户端再读就会触发 ERROR_BROKEN_PIPE,这时必须立刻 break,否则会卡死在循环里空耗 CPU。 下面这一行是实战里常用的兜底判断:只要全局变量 gvar0 存在,或者系统返回的错误码等于 ERROR_BROKEN_PIPE,就直接跳出读取循环,随后 pipe.Close() 释放句柄。 GlobalVariableCheck 返回 true 说明外部程序已经置了退出标志,而 ERROR_BROKEN_PIPE 是管道断裂的内核级报错,两者任一成立都代表链路不可用了。外汇与贵金属品种波动大、EA 常需长开,这种判定能避免 MT5 终端在断线重连时挂起。

MQL5 / C++
if(GlobalVariableCheck("gvar0")==true || (GetError()==ERROR_BROKEN_PIPE)) break;

用命名管道把逐笔报价甩出 MT5

想让外部程序实时吃到 MT5 的逐笔报价,最轻量的做法是在指标里开一条命名管道。初始化阶段先把管道建起来,OnInit() 里用 AccountInfoInteger(ACCOUNT_LOGIN) 生成的登录账号作管道名,循环等待管道可用,最多靠全局变量 gvar1 介入打断重试。 每次行情推演触发 OnCalculate(),代码先自增计数器 ctx,再用 SymbolInfoTick() 抓取当前品种的 MqlTick 结构,紧接着 pipe.WriteTick() 把这笔报价写进管道。日志里会打印类似「1 即时价通过 SendTickPipeClick 发送至服务器.」这样的行,你在 MT5 终端专家标签能直接数出每秒推送了几笔。 这套机制没有中间文件、不走网络端口,延迟通常低于一个 tick 间隔;但外汇和贵金属杠杆高、滑点随机,管道另一端若处理不过来可能丢 Tick,实盘前务必在模拟账户压测。 别把重试逻辑当摆设 OnInit 里的 while(!pipe.Open(...)) 如果对面程序没启动,指标会卡在打印「管道未创建, 5 秒内重试...」的循环里。真要自动化跑,记得先开接收端,或者靠 GlobalVariableCheck("gvar1") 主动 break,否则图表加载就假死。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 SendTickPipeIndicator.mq5 |
class=class="str">"cmt">//|                                                                 Copyright class="num">2010, Investeo.pl |
class=class="str">"cmt">//|                                                                 http:/Investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2010, Investeo.pl"
class="macro">#class="kw">property link      "http:/Investeo.pl"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#include <CNamedPipes.mqh>
CNamedPipe pipe;
class="type">int ctx;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {

   while (!pipe.Open(AccountInfoInteger(ACCOUNT_LOGIN)))
   {
      Print("管道未创建, class="num">5 秒内重试...");
      if (GlobalVariableCheck("gvar1")==true) break;
   }

   ctx = class="num">0;
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime& time[],
                const class="type">class="kw">double& open[],
                const class="type">class="kw">double& high[],
                const class="type">class="kw">double& low[],
                const class="type">class="kw">double& close[],
                const class="type">long& tick_volume[],
                const class="type">long& volume[],
                const class="type">int& spread[])
  {
   ctx++;
   class="type">MqlTick outgoing;
   SymbolInfoTick(Symbol(), outgoing);
   pipe.WriteTick(outgoing);
   Print(IntegerToString(ctx)+" 即时价通过 SendTickPipeClick 发送至服务器.");
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------

◍ 多源报价在单客户端里的管道分发

想在单个 MT5 客户端里同时看多个提供方的价格变动,靠的是命名管道把实时报价推给各自的变动指标。服务端在收到 tick 后调用 EventChartCustom(),指标侧在 OnChartEvent() 里接住自定义事件再把变动画出来。 报价以分号分隔的字符串传递,例如 '1.20223;1.20225' 这种 Bid;Ask 格式。指标只在事件 id 等于 CHARTEVENT_CUSTOM+6666 且 dparam 等于管道编号时才处理,避免把别的自定义事件误当报价。 实测布局里可挂三个变动指标:两个走管道收外部源,第三个不接管道、仅做原生 tick 对照,用来察觉是否有报价在转发环节丢失。外汇与贵金属报价转发涉及高杠杆与断线风险,管道断开时服务端会打印 '客户端从管道断开' 并 break 重连。 下面这段脚本是管道服务端核心:建管道、等连接、循环读 tick 并回显计数。把 account 输入改成你的账号号,就能在 MT5 里跑起来验证转发是否通。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     NamedPipeServerBroadcaster.mq5 |
class=class="str">"cmt">//|                 Copyright class="num">2010, Investeo.pl |
class=class="str">"cmt">//|                      http:/Investeo.pl |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2010, Investeo.pl"
class="macro">#class="kw">property link      "http:/Investeo.pl"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class="macro">#include <CNamedPipes.mqh>
input class="type">int account = class="num">0;
CNamedPipe pipe;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 交易程序初始函数                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">bool tickReceived;
   class="type">int i=class="num">0;
   if(pipe.Create(account)==true)
      while(GlobalVariableCheck("gvar0")==class="kw">false)
        {
         if(pipe.Connect()==true)
            Print("管道已连接");
            i=class="num">0;
         while(true)
           {
            do
              {
               tickReceived=pipe.ReadTick();
               if(tickReceived==class="kw">false)
                 {
                  if(kernel32::GetLastError()==ERROR_BROKEN_PIPE)
                    {
                     Print("客户端从管道断开 "+pipe.GetPipeName());
                     pipe.Disconnect();
                     break;
                    }
                 } else  {
                  i++; Print(IntegerToString(i)+" 服务器收到即时价.");
                  class="type">class="kw">string bidask=DoubleToString(pipe.incoming.bid)+";"+DoubleToString(pipe.incoming.ask);
让小布替你跑这套
多终端报价同步的管道状态监控,小布盯盘的 AIGC 已内置,打开对应品种页即可看到延迟与丢包估计,你只管判断跨终端套利窗口。

常见问题

匿名管道要求两进程有父子关系且不能走网络;命名管道可用已知名称跨无关进程甚至内网通信,更适合独立 MT5 终端互联。
可以,小布盯盘的品种页把多源报价管道的健康度做了可视化,省去你自己写心跳日志的功夫。
不必,这只是作者区分客户端的习惯;只要服务端和客户端约定一致且符合 \\server\pipe\name 格式即可。
文件读写有锁竞争且单消息开销大,持续报价流下延迟与出错率明显上升,管道是更轻量的 IPC 选择。